From: Dave Airlie Date: Mon, 31 Jan 2022 06:33:54 +0000 (+1000) Subject: Merge tag 'drm-misc-next-2022-01-27' of git://anongit.freedesktop.org/drm/drm-misc... X-Git-Url: https://repo.jachan.dev/J-linux.git/commitdiff_plain/53dbee4926d3706ca9e03f3928fa85b5ec3bc0cc Merge tag 'drm-misc-next-2022-01-27' of git://anongit.freedesktop.org/drm/drm-misc into drm-next [airlied: add two missing Kconfig] drm-misc-next for v5.18: UAPI Changes: - Fix invalid IN_FORMATS blob when plane->format_mod_supported is NULL. Cross-subsystem Changes: - Assorted dt bindings updates. - Fix vga16fb vga checking on x86. - Fix extra semicolon in rwsem.h's _down_write_nest_lock. - Assorted small fixes to agp and fbdev drivers. - Fix oops in creating a udmabuf with 0 pages. - Hot-unplug firmware fb devices on forced removal - Reqquest memory region in simplefb and simpledrm, and don't make the ioresource as busy. Core Changes: - Mock a drm_plane in drm-plane-helper selftest. - Assorted bug fixes to device logging, dbi. - Use DP helper for sink count in mst. - Assorted documentation fixes. - Assorted small fixes. - Move DP headers to drm/dp, and add a drm dp helper module. - Move the buddy allocator from i915 to common drm. - Add simple pci and platform module init macros to remove a lot of boilerplate from some drivers. - Support microsoft extension for HMDs and specialized monitors. - Improve edid parser's deep color handling. - Add type 7 timing support to edid parser. - Add a weak backpointer to the ttm_bo from ttm_resource - Add 3 eDP panels. Driver Changes: - Add support for HDMI and JZ4780 to ingenic. - Add support for higher DP/eDP bitrates to nouveau. - Assorted driver fixes to tilcdc, vmwgfx, sn65dsi83, meson, stm, panfrost, v3d, gma500, vc4, virtio, mgag200, ast, radeon, amdgpu, nouveau, various bridge drivers. - Convert and revert exynos dsi support to bridge driver. - Add vcc supply regulator support for sn65dsi83. - More conversion of bridge/chipone-icn6211 to atomic. - Remove conflicting fb's from stm, and add support for new hw version. - Add device link in parade-ps8640 to fix suspend/resume. - Update Boe-tv110c9m init sequence. - Add wide screen support to AST2600. - Fix omapdrm implicit dma_buf fencing. - Add support for multiple overlay planes to vkms. - Convert bridge/anx7625 to atomic, add HDCP support, add eld support for audio, and fix HPD. - Add driver for ChromeOS privacy screen. - Handover display from firmware to vc4 more gracefully, and support nomodeset. - Add flexible and ycbcr pixel formats to stm/ltdc. - Convert exynos mipi dsi to atomic. - Add initial dual core group GPUs support to panfrost. - No longer add exclusive fence in amdgpu as shared fence. - Add CSC and full range supoprt to vc4. - Shutdown the display on system shutdown and unbind. - Add Multi-Inno Technology MI0700S4T-6 simple panel. Signed-off-by: Dave Airlie From: Maarten Lankhorst Link: https://patchwork.freedesktop.org/patch/msgid/456a23c6-7324-7543-0c45-751f30ef83f7@linux.intel.com --- 53dbee4926d3706ca9e03f3928fa85b5ec3bc0cc diff --cc drivers/gpu/drm/dp/drm_dp_mst_topology.c index 000000000000,ddb9aa051288..11300b53d24f mode 000000,100644..100644 --- a/drivers/gpu/drm/dp/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/dp/drm_dp_mst_topology.c @@@ -1,0 -1,5977 +1,5978 @@@ + /* + * Copyright © 2014 Red Hat + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + #include + #include + #include + #include + #endif + + #include + #include + #include + #include + #include + #include + + #include "drm_dp_helper_internal.h" + #include "drm_dp_mst_topology_internal.h" + + /** + * DOC: dp mst helper + * + * These functions contain parts of the DisplayPort 1.2a MultiStream Transport + * protocol. The helpers contain a topology manager and bandwidth manager. + * The helpers encapsulate the sending and received of sideband msgs. + */ + struct drm_dp_pending_up_req { + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_req_body msg; + struct list_head next; + }; + + static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf); + + static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port); + + static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload); + + static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); + static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); + + static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + + static void + drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + + static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port); + static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid); + + static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port); + static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port); + static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr); + + static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch); + + #define DBG_PREFIX "[dp_mst]" + + #define DP_STR(x) [DP_ ## x] = #x + + static const char *drm_dp_mst_req_type_str(u8 req_type) + { + static const char * const req_type_str[] = { + DP_STR(GET_MSG_TRANSACTION_VERSION), + DP_STR(LINK_ADDRESS), + DP_STR(CONNECTION_STATUS_NOTIFY), + DP_STR(ENUM_PATH_RESOURCES), + DP_STR(ALLOCATE_PAYLOAD), + DP_STR(QUERY_PAYLOAD), + DP_STR(RESOURCE_STATUS_NOTIFY), + DP_STR(CLEAR_PAYLOAD_ID_TABLE), + DP_STR(REMOTE_DPCD_READ), + DP_STR(REMOTE_DPCD_WRITE), + DP_STR(REMOTE_I2C_READ), + DP_STR(REMOTE_I2C_WRITE), + DP_STR(POWER_UP_PHY), + DP_STR(POWER_DOWN_PHY), + DP_STR(SINK_EVENT_NOTIFY), + DP_STR(QUERY_STREAM_ENC_STATUS), + }; + + if (req_type >= ARRAY_SIZE(req_type_str) || + !req_type_str[req_type]) + return "unknown"; + + return req_type_str[req_type]; + } + + #undef DP_STR + #define DP_STR(x) [DP_NAK_ ## x] = #x + + static const char *drm_dp_mst_nak_reason_str(u8 nak_reason) + { + static const char * const nak_reason_str[] = { + DP_STR(WRITE_FAILURE), + DP_STR(INVALID_READ), + DP_STR(CRC_FAILURE), + DP_STR(BAD_PARAM), + DP_STR(DEFER), + DP_STR(LINK_FAILURE), + DP_STR(NO_RESOURCES), + DP_STR(DPCD_FAIL), + DP_STR(I2C_NAK), + DP_STR(ALLOCATE_FAIL), + }; + + if (nak_reason >= ARRAY_SIZE(nak_reason_str) || + !nak_reason_str[nak_reason]) + return "unknown"; + + return nak_reason_str[nak_reason]; + } + + #undef DP_STR + #define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x + + static const char *drm_dp_mst_sideband_tx_state_str(int state) + { + static const char * const sideband_reason_str[] = { + DP_STR(QUEUED), + DP_STR(START_SEND), + DP_STR(SENT), + DP_STR(RX), + DP_STR(TIMEOUT), + }; + + if (state >= ARRAY_SIZE(sideband_reason_str) || + !sideband_reason_str[state]) + return "unknown"; + + return sideband_reason_str[state]; + } + + static int + drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len) + { + int i; + u8 unpacked_rad[16]; + + for (i = 0; i < lct; i++) { + if (i % 2) + unpacked_rad[i] = rad[i / 2] >> 4; + else + unpacked_rad[i] = rad[i / 2] & BIT_MASK(4); + } + + /* TODO: Eventually add something to printk so we can format the rad + * like this: 1.2.3 + */ + return snprintf(out, len, "%*phC", lct, unpacked_rad); + } + + /* sideband msg handling */ + static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles) + { + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = num_nibbles * 4; + u8 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x10) == 0x10) + remainder ^= 0x13; + } + + number_of_bits = 4; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x10) != 0) + remainder ^= 0x13; + } + + return remainder; + } + + static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes) + { + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = number_of_bytes * 8; + u16 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x100) == 0x100) + remainder ^= 0xd5; + } + + number_of_bits = 8; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x100) != 0) + remainder ^= 0xd5; + } + + return remainder & 0xff; + } + static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr) + { + u8 size = 3; + + size += (hdr->lct / 2); + return size; + } + + static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int *len) + { + int idx = 0; + int i; + u8 crc4; + + buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf); + for (i = 0; i < (hdr->lct / 2); i++) + buf[idx++] = hdr->rad[i]; + buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) | + (hdr->msg_len & 0x3f); + buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4); + + crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1); + buf[idx - 1] |= (crc4 & 0xf); + + *len = idx; + } + + static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int buflen, u8 *hdrlen) + { + u8 crc4; + u8 len; + int i; + u8 idx; + + if (buf[0] == 0) + return false; + len = 3; + len += ((buf[0] & 0xf0) >> 4) / 2; + if (len > buflen) + return false; + crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1); + + if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) { + drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]); + return false; + } + + hdr->lct = (buf[0] & 0xf0) >> 4; + hdr->lcr = (buf[0] & 0xf); + idx = 1; + for (i = 0; i < (hdr->lct / 2); i++) + hdr->rad[i] = buf[idx++]; + hdr->broadcast = (buf[idx] >> 7) & 0x1; + hdr->path_msg = (buf[idx] >> 6) & 0x1; + hdr->msg_len = buf[idx] & 0x3f; + idx++; + hdr->somt = (buf[idx] >> 7) & 0x1; + hdr->eomt = (buf[idx] >> 6) & 0x1; + hdr->seqno = (buf[idx] >> 4) & 0x1; + idx++; + *hdrlen = idx; + return true; + } + + void + drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, + struct drm_dp_sideband_msg_tx *raw) + { + int idx = 0; + int i; + u8 *buf = raw->msg; + + buf[idx++] = req->req_type & 0x7f; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + buf[idx] = (req->u.port_num.port_number & 0xf) << 4; + idx++; + break; + case DP_ALLOCATE_PAYLOAD: + buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 | + (req->u.allocate_payload.number_sdp_streams & 0xf); + idx++; + buf[idx] = (req->u.allocate_payload.vcpi & 0x7f); + idx++; + buf[idx] = (req->u.allocate_payload.pbn >> 8); + idx++; + buf[idx] = (req->u.allocate_payload.pbn & 0xff); + idx++; + for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) { + buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) | + (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf); + idx++; + } + if (req->u.allocate_payload.number_sdp_streams & 1) { + i = req->u.allocate_payload.number_sdp_streams - 1; + buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4; + idx++; + } + break; + case DP_QUERY_PAYLOAD: + buf[idx] = (req->u.query_payload.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.query_payload.vcpi & 0x7f); + idx++; + break; + case DP_REMOTE_DPCD_READ: + buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_read.num_bytes); + idx++; + break; + + case DP_REMOTE_DPCD_WRITE: + buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes); + idx += req->u.dpcd_write.num_bytes; + break; + case DP_REMOTE_I2C_READ: + buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4; + buf[idx] |= (req->u.i2c_read.num_transactions & 0x3); + idx++; + for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) { + buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f; + idx++; + buf[idx] = req->u.i2c_read.transactions[i].num_bytes; + idx++; + memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes); + idx += req->u.i2c_read.transactions[i].num_bytes; + + buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4; + buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf); + idx++; + } + buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_read.num_bytes_read); + idx++; + break; + + case DP_REMOTE_I2C_WRITE: + buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes); + idx += req->u.i2c_write.num_bytes; + break; + case DP_QUERY_STREAM_ENC_STATUS: { + const struct drm_dp_query_stream_enc_status *msg; + + msg = &req->u.enc_status; + buf[idx] = msg->stream_id; + idx++; + memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id)); + idx += sizeof(msg->client_id); + buf[idx] = 0; + buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event); + buf[idx] |= msg->valid_stream_event ? BIT(2) : 0; + buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior); + buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0; + idx++; + } + break; + } + raw->cur_len = idx; + } + EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req); + + /* Decode a sideband request we've encoded, mainly used for debugging */ + int + drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, + struct drm_dp_sideband_msg_req_body *req) + { + const u8 *buf = raw->msg; + int i, idx = 0; + + req->req_type = buf[idx++] & 0x7f; + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; + break; + case DP_ALLOCATE_PAYLOAD: + { + struct drm_dp_allocate_payload *a = + &req->u.allocate_payload; + + a->number_sdp_streams = buf[idx] & 0xf; + a->port_number = (buf[idx] >> 4) & 0xf; + + WARN_ON(buf[++idx] & 0x80); + a->vcpi = buf[idx] & 0x7f; + + a->pbn = buf[++idx] << 8; + a->pbn |= buf[++idx]; + + idx++; + for (i = 0; i < a->number_sdp_streams; i++) { + a->sdp_stream_sink[i] = + (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf; + } + } + break; + case DP_QUERY_PAYLOAD: + req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf; + WARN_ON(buf[++idx] & 0x80); + req->u.query_payload.vcpi = buf[idx] & 0x7f; + break; + case DP_REMOTE_DPCD_READ: + { + struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read; + + r->port_number = (buf[idx] >> 4) & 0xf; + + r->dpcd_address = (buf[idx] << 16) & 0xf0000; + r->dpcd_address |= (buf[++idx] << 8) & 0xff00; + r->dpcd_address |= buf[++idx] & 0xff; + + r->num_bytes = buf[++idx]; + } + break; + case DP_REMOTE_DPCD_WRITE: + { + struct drm_dp_remote_dpcd_write *w = + &req->u.dpcd_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + + w->dpcd_address = (buf[idx] << 16) & 0xf0000; + w->dpcd_address |= (buf[++idx] << 8) & 0xff00; + w->dpcd_address |= buf[++idx] & 0xff; + + w->num_bytes = buf[++idx]; + + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_REMOTE_I2C_READ: + { + struct drm_dp_remote_i2c_read *r = &req->u.i2c_read; + struct drm_dp_remote_i2c_read_tx *tx; + bool failed = false; + + r->num_transactions = buf[idx] & 0x3; + r->port_number = (buf[idx] >> 4) & 0xf; + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + + tx->i2c_dev_id = buf[++idx] & 0x7f; + tx->num_bytes = buf[++idx]; + tx->bytes = kmemdup(&buf[++idx], + tx->num_bytes, + GFP_KERNEL); + if (!tx->bytes) { + failed = true; + break; + } + idx += tx->num_bytes; + tx->no_stop_bit = (buf[idx] >> 5) & 0x1; + tx->i2c_transaction_delay = buf[idx] & 0xf; + } + + if (failed) { + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + kfree(tx->bytes); + } + return -ENOMEM; + } + + r->read_i2c_device_id = buf[++idx] & 0x7f; + r->num_bytes_read = buf[++idx]; + } + break; + case DP_REMOTE_I2C_WRITE: + { + struct drm_dp_remote_i2c_write *w = &req->u.i2c_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + w->write_i2c_device_id = buf[++idx] & 0x7f; + w->num_bytes = buf[++idx]; + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_QUERY_STREAM_ENC_STATUS: + req->u.enc_status.stream_id = buf[idx++]; + for (i = 0; i < sizeof(req->u.enc_status.client_id); i++) + req->u.enc_status.client_id[i] = buf[idx++]; + + req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0), + buf[idx]); + req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2), + buf[idx]); + req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3), + buf[idx]); + req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5), + buf[idx]); + break; + } + + return 0; + } + EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req); + + void + drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, + int indent, struct drm_printer *printer) + { + int i; + + #define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__) + if (req->req_type == DP_LINK_ADDRESS) { + /* No contents to print */ + P("type=%s\n", drm_dp_mst_req_type_str(req->req_type)); + return; + } + + P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type)); + indent++; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + P("port=%d\n", req->u.port_num.port_number); + break; + case DP_ALLOCATE_PAYLOAD: + P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n", + req->u.allocate_payload.port_number, + req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.sdp_stream_sink); + break; + case DP_QUERY_PAYLOAD: + P("port=%d vcpi=%d\n", + req->u.query_payload.port_number, + req->u.query_payload.vcpi); + break; + case DP_REMOTE_DPCD_READ: + P("port=%d dpcd_addr=%05x len=%d\n", + req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address, + req->u.dpcd_read.num_bytes); + break; + case DP_REMOTE_DPCD_WRITE: + P("port=%d addr=%05x len=%d: %*ph\n", + req->u.dpcd_write.port_number, + req->u.dpcd_write.dpcd_address, + req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes, + req->u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + P("port=%d num_tx=%d id=%d size=%d:\n", + req->u.i2c_read.port_number, + req->u.i2c_read.num_transactions, + req->u.i2c_read.read_i2c_device_id, + req->u.i2c_read.num_bytes_read); + + indent++; + for (i = 0; i < req->u.i2c_read.num_transactions; i++) { + const struct drm_dp_remote_i2c_read_tx *rtx = + &req->u.i2c_read.transactions[i]; + + P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n", + i, rtx->i2c_dev_id, rtx->num_bytes, + rtx->no_stop_bit, rtx->i2c_transaction_delay, + rtx->num_bytes, rtx->bytes); + } + break; + case DP_REMOTE_I2C_WRITE: + P("port=%d id=%d size=%d: %*ph\n", + req->u.i2c_write.port_number, + req->u.i2c_write.write_i2c_device_id, + req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes, + req->u.i2c_write.bytes); + break; + case DP_QUERY_STREAM_ENC_STATUS: + P("stream_id=%u client_id=%*ph stream_event=%x " + "valid_event=%d stream_behavior=%x valid_behavior=%d", + req->u.enc_status.stream_id, + (int)ARRAY_SIZE(req->u.enc_status.client_id), + req->u.enc_status.client_id, req->u.enc_status.stream_event, + req->u.enc_status.valid_stream_event, + req->u.enc_status.stream_behavior, + req->u.enc_status.valid_stream_behavior); + break; + default: + P("???\n"); + break; + } + #undef P + } + EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body); + + static inline void + drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p, + const struct drm_dp_sideband_msg_tx *txmsg) + { + struct drm_dp_sideband_msg_req_body req; + char buf[64]; + int ret; + int i; + + drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf, + sizeof(buf)); + drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n", + txmsg->cur_offset, txmsg->cur_len, txmsg->seqno, + drm_dp_mst_sideband_tx_state_str(txmsg->state), + txmsg->path_msg, buf); + + ret = drm_dp_decode_sideband_req(txmsg, &req); + if (ret) { + drm_printf(p, "\n", ret); + return; + } + drm_dp_dump_sideband_msg_req_body(&req, 1, p); + + switch (req.req_type) { + case DP_REMOTE_DPCD_WRITE: + kfree(req.u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + for (i = 0; i < req.u.i2c_read.num_transactions; i++) + kfree(req.u.i2c_read.transactions[i].bytes); + break; + case DP_REMOTE_I2C_WRITE: + kfree(req.u.i2c_write.bytes); + break; + } + } + + static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len) + { + u8 crc4; + + crc4 = drm_dp_msg_data_crc4(msg, len); + msg[len] = crc4; + } + + static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep, + struct drm_dp_sideband_msg_tx *raw) + { + int idx = 0; + u8 *buf = raw->msg; + + buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f); + + raw->cur_len = idx; + } + + static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, + struct drm_dp_sideband_msg_hdr *hdr, + u8 hdrlen) + { + /* + * ignore out-of-order messages or messages that are part of a + * failed transaction + */ + if (!hdr->somt && !msg->have_somt) + return false; + + /* get length contained in this portion */ + msg->curchunk_idx = 0; + msg->curchunk_len = hdr->msg_len; + msg->curchunk_hdrlen = hdrlen; + + /* we have already gotten an somt - don't bother parsing */ + if (hdr->somt && msg->have_somt) + return false; + + if (hdr->somt) { + memcpy(&msg->initial_hdr, hdr, + sizeof(struct drm_dp_sideband_msg_hdr)); + msg->have_somt = true; + } + if (hdr->eomt) + msg->have_eomt = true; + + return true; + } + + /* this adds a chunk of msg to the builder to get the final msg */ + static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg, + u8 *replybuf, u8 replybuflen) + { + u8 crc4; + + memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen); + msg->curchunk_idx += replybuflen; + + if (msg->curchunk_idx >= msg->curchunk_len) { + /* do CRC */ + crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1); + if (crc4 != msg->chunk[msg->curchunk_len - 1]) + print_hex_dump(KERN_DEBUG, "wrong crc", + DUMP_PREFIX_NONE, 16, 1, + msg->chunk, msg->curchunk_len, false); + /* copy chunk into bigger msg */ + memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1); + msg->curlen += msg->curchunk_len - 1; + } + return true; + } + + static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + int i; + + memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16); + idx += 16; + repmsg->u.link_addr.nports = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + for (i = 0; i < repmsg->u.link_addr.nports; i++) { + if (raw->msg[idx] & 0x80) + repmsg->u.link_addr.ports[i].input_port = 1; + + repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7; + repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf); + + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1; + repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1; + if (repmsg->u.link_addr.ports[i].input_port == 0) + repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + if (repmsg->u.link_addr.ports[i].input_port == 0) { + repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]); + idx++; + if (idx > raw->curlen) + goto fail_len; + memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf); + idx++; + + } + if (idx > raw->curlen) + goto fail_len; + } + + return true; + fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); + return true; + fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + return true; + fail_len: + DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf); + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; + idx++; + /* TODO check */ + memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); + return true; + fail_len: + DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; + fail_len: + DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.vcpi = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; + fail_len: + DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; + fail_len: + DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + int idx = 1; + + repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) { + DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n", + idx, raw->curlen); + return false; + } + return true; + } + + static bool + drm_dp_sideband_parse_query_stream_enc_status( + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) + { + struct drm_dp_query_stream_enc_status_ack_reply *reply; + + reply = &repmsg->u.enc_status; + + reply->stream_id = raw->msg[3]; + + reply->reply_signed = raw->msg[2] & BIT(0); + + /* + * NOTE: It's my impression from reading the spec that the below parsing + * is correct. However I noticed while testing with an HDCP 1.4 display + * through an HDCP 2.2 hub that only bit 3 was set. In that case, I + * would expect both bits to be set. So keep the parsing following the + * spec, but beware reality might not match the spec (at least for some + * configurations). + */ + reply->hdcp_1x_device_present = raw->msg[2] & BIT(4); + reply->hdcp_2x_device_present = raw->msg[2] & BIT(3); + + reply->query_capable_device_present = raw->msg[2] & BIT(5); + reply->legacy_device_present = raw->msg[2] & BIT(6); + reply->unauthorizable_device_present = raw->msg[2] & BIT(7); + + reply->auth_completed = !!(raw->msg[1] & BIT(3)); + reply->encryption_enabled = !!(raw->msg[1] & BIT(4)); + reply->repeater_present = !!(raw->msg[1] & BIT(5)); + reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6; + + return true; + } + + static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *msg) + { + memset(msg, 0, sizeof(*msg)); + msg->reply_type = (raw->msg[0] & 0x80) >> 7; + msg->req_type = (raw->msg[0] & 0x7f); + + if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) { + memcpy(msg->u.nak.guid, &raw->msg[1], 16); + msg->u.nak.reason = raw->msg[17]; + msg->u.nak.nak_data = raw->msg[18]; + return false; + } + + switch (msg->req_type) { + case DP_LINK_ADDRESS: + return drm_dp_sideband_parse_link_address(mgr, raw, msg); + case DP_QUERY_PAYLOAD: + return drm_dp_sideband_parse_query_payload_ack(raw, msg); + case DP_REMOTE_DPCD_READ: + return drm_dp_sideband_parse_remote_dpcd_read(raw, msg); + case DP_REMOTE_DPCD_WRITE: + return drm_dp_sideband_parse_remote_dpcd_write(raw, msg); + case DP_REMOTE_I2C_READ: + return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg); + case DP_REMOTE_I2C_WRITE: + return true; /* since there's nothing to parse */ + case DP_ENUM_PATH_RESOURCES: + return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg); + case DP_ALLOCATE_PAYLOAD: + return drm_dp_sideband_parse_allocate_payload_ack(raw, msg); + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg); + case DP_CLEAR_PAYLOAD_ID_TABLE: + return true; /* since there's nothing to parse */ + case DP_QUERY_STREAM_ENC_STATUS: + return drm_dp_sideband_parse_query_stream_enc_status(raw, msg); + default: + drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } + } + + static bool + drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) + { + int idx = 1; + + msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1; + msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1; + msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1; + msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7); + idx++; + return true; + fail_len: + drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n", + idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) + { + int idx = 1; + + msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx++; + return true; + fail_len: + drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen); + return false; + } + + static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) + { + memset(msg, 0, sizeof(*msg)); + msg->req_type = (raw->msg[0] & 0x7f); + + switch (msg->req_type) { + case DP_CONNECTION_STATUS_NOTIFY: + return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg); + case DP_RESOURCE_STATUS_NOTIFY: + return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg); + default: + drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } + } + + static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes, u8 *bytes) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_WRITE; + req.u.dpcd_write.port_number = port_num; + req.u.dpcd_write.dpcd_address = offset; + req.u.dpcd_write.num_bytes = num_bytes; + req.u.dpcd_write.bytes = bytes; + drm_dp_encode_sideband_req(&req, msg); + } + + static void build_link_address(struct drm_dp_sideband_msg_tx *msg) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_LINK_ADDRESS; + drm_dp_encode_sideband_req(&req, msg); + } + + static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + } + + static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, + int port_num) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_ENUM_PATH_RESOURCES; + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + return 0; + } + + static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, + int port_num, + u8 vcpi, uint16_t pbn, + u8 number_sdp_streams, + u8 *sdp_stream_sink) + { + struct drm_dp_sideband_msg_req_body req; + + memset(&req, 0, sizeof(req)); + req.req_type = DP_ALLOCATE_PAYLOAD; + req.u.allocate_payload.port_number = port_num; + req.u.allocate_payload.vcpi = vcpi; + req.u.allocate_payload.pbn = pbn; + req.u.allocate_payload.number_sdp_streams = number_sdp_streams; + memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink, + number_sdp_streams); + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + } + + static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg, + int port_num, bool power_up) + { + struct drm_dp_sideband_msg_req_body req; + + if (power_up) + req.req_type = DP_POWER_UP_PHY; + else + req.req_type = DP_POWER_DOWN_PHY; + + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + } + + static int + build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id, + u8 *q_id) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_QUERY_STREAM_ENC_STATUS; + req.u.enc_status.stream_id = stream_id; + memcpy(req.u.enc_status.client_id, q_id, + sizeof(req.u.enc_status.client_id)); + req.u.enc_status.stream_event = 0; + req.u.enc_status.valid_stream_event = false; + req.u.enc_status.stream_behavior = 0; + req.u.enc_status.valid_stream_behavior = false; + + drm_dp_encode_sideband_req(&req, msg); + return 0; + } + + static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi) + { + int ret, vcpi_ret; + + mutex_lock(&mgr->payload_lock); + ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); + if (ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret); + goto out_unlock; + } + + vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); + if (vcpi_ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret); + goto out_unlock; + } + + set_bit(ret, &mgr->payload_mask); + set_bit(vcpi_ret, &mgr->vcpi_mask); + vcpi->vcpi = vcpi_ret + 1; + mgr->proposed_vcpis[ret - 1] = vcpi; + out_unlock: + mutex_unlock(&mgr->payload_lock); + return ret; + } + + static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, + int vcpi) + { + int i; + + if (vcpi == 0) + return; + + mutex_lock(&mgr->payload_lock); + drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi); + clear_bit(vcpi - 1, &mgr->vcpi_mask); + + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i] && + mgr->proposed_vcpis[i]->vcpi == vcpi) { + mgr->proposed_vcpis[i] = NULL; + clear_bit(i + 1, &mgr->payload_mask); + } + } + mutex_unlock(&mgr->payload_lock); + } + + static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) + { + unsigned int state; + + /* + * All updates to txmsg->state are protected by mgr->qlock, and the two + * cases we check here are terminal states. For those the barriers + * provided by the wake_up/wait_event pair are enough. + */ + state = READ_ONCE(txmsg->state); + return (state == DRM_DP_SIDEBAND_TX_RX || + state == DRM_DP_SIDEBAND_TX_TIMEOUT); + } + + static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, + struct drm_dp_sideband_msg_tx *txmsg) + { + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + unsigned long wait_timeout = msecs_to_jiffies(4000); + unsigned long wait_expires = jiffies + wait_timeout; + int ret; + + for (;;) { + /* + * If the driver provides a way for this, change to + * poll-waiting for the MST reply interrupt if we didn't receive + * it for 50 msec. This would cater for cases where the HPD + * pulse signal got lost somewhere, even though the sink raised + * the corresponding MST interrupt correctly. One example is the + * Club 3D CAC-1557 TypeC -> DP adapter which for some reason + * filters out short pulses with a duration less than ~540 usec. + * + * The poll period is 50 msec to avoid missing an interrupt + * after the sink has cleared it (after a 110msec timeout + * since it raised the interrupt). + */ + ret = wait_event_timeout(mgr->tx_waitq, + check_txmsg_state(mgr, txmsg), + mgr->cbs->poll_hpd_irq ? + msecs_to_jiffies(50) : + wait_timeout); + + if (ret || !mgr->cbs->poll_hpd_irq || + time_after(jiffies, wait_expires)) + break; + + mgr->cbs->poll_hpd_irq(mgr); + } + + mutex_lock(&mgr->qlock); + if (ret > 0) { + if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) { + ret = -EIO; + goto out; + } + } else { + drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n", + txmsg, txmsg->state, txmsg->seqno); + + /* dump some state */ + ret = -EIO; + + /* remove from q */ + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED || + txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND || + txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + list_del(&txmsg->next); + } + out: + if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + mutex_unlock(&mgr->qlock); + + drm_dp_mst_kick_tx(mgr); + return ret; + } + + static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) + { + struct drm_dp_mst_branch *mstb; + + mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); + if (!mstb) + return NULL; + + mstb->lct = lct; + if (lct > 1) + memcpy(mstb->rad, rad, lct / 2); + INIT_LIST_HEAD(&mstb->ports); + kref_init(&mstb->topology_kref); + kref_init(&mstb->malloc_kref); + return mstb; + } + + static void drm_dp_free_mst_branch_device(struct kref *kref) + { + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, malloc_kref); + + if (mstb->port_parent) + drm_dp_mst_put_port_malloc(mstb->port_parent); + + kfree(mstb); + } + + /** + * DOC: Branch device and port refcounting + * + * Topology refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The refcounting schemes for &struct drm_dp_mst_branch and &struct + * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have + * two different kinds of refcounts: topology refcounts, and malloc refcounts. + * + * Topology refcounts are not exposed to drivers, and are handled internally + * by the DP MST helpers. The helpers use them in order to prevent the + * in-memory topology state from being changed in the middle of critical + * operations like changing the internal state of payload allocations. This + * means each branch and port will be considered to be connected to the rest + * of the topology until its topology refcount reaches zero. Additionally, + * for ports this means that their associated &struct drm_connector will stay + * registered with userspace until the port's refcount reaches 0. + * + * Malloc refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Malloc references are used to keep a &struct drm_dp_mst_port or &struct + * drm_dp_mst_branch allocated even after all of its topology references have + * been dropped, so that the driver or MST helpers can safely access each + * branch's last known state before it was disconnected from the topology. + * When the malloc refcount of a port or branch reaches 0, the memory + * allocation containing the &struct drm_dp_mst_branch or &struct + * drm_dp_mst_port respectively will be freed. + * + * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed + * to drivers. As of writing this documentation, there are no drivers that + * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST + * helpers. Exposing this API to drivers in a race-free manner would take more + * tweaking of the refcounting scheme, however patches are welcome provided + * there is a legitimate driver usecase for this. + * + * Refcount relationships in a topology + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Let's take a look at why the relationship between topology and malloc + * refcounts is designed the way it is. + * + * .. kernel-figure:: dp-mst/topology-figure-1.dot + * + * An example of topology and malloc refs in a DP MST topology with two + * active payloads. Topology refcount increments are indicated by solid + * lines, and malloc refcount increments are indicated by dashed lines. + * Each starts from the branch which incremented the refcount, and ends at + * the branch to which the refcount belongs to, i.e. the arrow points the + * same way as the C pointers used to reference a structure. + * + * As you can see in the above figure, every branch increments the topology + * refcount of its children, and increments the malloc refcount of its + * parent. Additionally, every payload increments the malloc refcount of its + * assigned port by 1. + * + * So, what would happen if MSTB #3 from the above figure was unplugged from + * the system, but the driver hadn't yet removed payload #2 from port #3? The + * topology would start to look like the figure below. + * + * .. kernel-figure:: dp-mst/topology-figure-2.dot + * + * Ports and branch devices which have been released from memory are + * colored grey, and references which have been removed are colored red. + * + * Whenever a port or branch device's topology refcount reaches zero, it will + * decrement the topology refcounts of all its children, the malloc refcount + * of its parent, and finally its own malloc refcount. For MSTB #4 and port + * #4, this means they both have been disconnected from the topology and freed + * from memory. But, because payload #2 is still holding a reference to port + * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port + * is still accessible from memory. This also means port #3 has not yet + * decremented the malloc refcount of MSTB #3, so its &struct + * drm_dp_mst_branch will also stay allocated in memory until port #3's + * malloc refcount reaches 0. + * + * This relationship is necessary because in order to release payload #2, we + * need to be able to figure out the last relative of port #3 that's still + * connected to the topology. In this case, we would travel up the topology as + * shown below. + * + * .. kernel-figure:: dp-mst/topology-figure-3.dot + * + * And finally, remove payload #2 by communicating with port #2 through + * sideband transactions. + */ + + /** + * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of + * + * Increments &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_put_mstb_malloc() + */ + static void + drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb) + { + kref_get(&mstb->malloc_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref)); + } + + /** + * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_get_mstb_malloc() + */ + static void + drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb) + { + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1); + kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device); + } + + static void drm_dp_free_mst_port(struct kref *kref) + { + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, malloc_kref); + + drm_dp_mst_put_mstb_malloc(port->parent); + kfree(port); + } + + /** + * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to increment the malloc refcount of + * + * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * Because @port could potentially be freed at any time by the DP MST helpers + * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this + * function, drivers that which to make use of &struct drm_dp_mst_port should + * ensure that they grab at least one main malloc reference to their MST ports + * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before + * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. + * + * See also: drm_dp_mst_put_port_malloc() + */ + void + drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port) + { + kref_get(&port->malloc_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref)); + } + EXPORT_SYMBOL(drm_dp_mst_get_port_malloc); + + /** + * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * See also: drm_dp_mst_get_port_malloc() + */ + void + drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port) + { + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1); + kref_put(&port->malloc_kref, drm_dp_free_mst_port); + } + EXPORT_SYMBOL(drm_dp_mst_put_port_malloc); + + #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + + #define STACK_DEPTH 8 + + static noinline void + __topology_ref_save(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_ref_history *history, + enum drm_dp_mst_topology_ref_type type) + { + struct drm_dp_mst_topology_ref_entry *entry = NULL; + depot_stack_handle_t backtrace; + ulong stack_entries[STACK_DEPTH]; + uint n; + int i; + + n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1); + backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); + if (!backtrace) + return; + + /* Try to find an existing entry for this backtrace */ + for (i = 0; i < history->len; i++) { + if (history->entries[i].backtrace == backtrace) { + entry = &history->entries[i]; + break; + } + } + + /* Otherwise add one */ + if (!entry) { + struct drm_dp_mst_topology_ref_entry *new; + int new_len = history->len + 1; + + new = krealloc(history->entries, sizeof(*new) * new_len, + GFP_KERNEL); + if (!new) + return; + + entry = &new[history->len]; + history->len = new_len; + history->entries = new; + + entry->backtrace = backtrace; + entry->type = type; + entry->count = 0; + } + entry->count++; + entry->ts_nsec = ktime_get_ns(); + } + + static int + topology_ref_history_cmp(const void *a, const void *b) + { + const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b; + + if (entry_a->ts_nsec > entry_b->ts_nsec) + return 1; + else if (entry_a->ts_nsec < entry_b->ts_nsec) + return -1; + else + return 0; + } + + static inline const char * + topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type) + { + if (type == DRM_DP_MST_TOPOLOGY_REF_GET) + return "get"; + else + return "put"; + } + + static void + __dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history, + void *ptr, const char *type_str) + { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + int i; + + if (!buf) + return; + + if (!history->len) + goto out; + + /* First, sort the list so that it goes from oldest to newest + * reference entry + */ + sort(history->entries, history->len, sizeof(*history->entries), + topology_ref_history_cmp, NULL); + + drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n", + type_str, ptr); + + for (i = 0; i < history->len; i++) { + const struct drm_dp_mst_topology_ref_entry *entry = + &history->entries[i]; + u64 ts_nsec = entry->ts_nsec; + u32 rem_nsec = do_div(ts_nsec, 1000000000); + + stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4); + + drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s", + entry->count, + topology_ref_type_to_str(entry->type), + ts_nsec, rem_nsec / 1000, buf); + } + + /* Now free the history, since this is the only time we expose it */ + kfree(history->entries); + out: + kfree(buf); + } + + static __always_inline void + drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) + { + __dump_topology_ref_history(&mstb->topology_ref_history, mstb, + "MSTB"); + } + + static __always_inline void + drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) + { + __dump_topology_ref_history(&port->topology_ref_history, port, + "Port"); + } + + static __always_inline void + save_mstb_topology_ref(struct drm_dp_mst_branch *mstb, + enum drm_dp_mst_topology_ref_type type) + { + __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type); + } + + static __always_inline void + save_port_topology_ref(struct drm_dp_mst_port *port, + enum drm_dp_mst_topology_ref_type type) + { + __topology_ref_save(port->mgr, &port->topology_ref_history, type); + } + + static inline void + topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) + { + mutex_lock(&mgr->topology_ref_history_lock); + } + + static inline void + topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) + { + mutex_unlock(&mgr->topology_ref_history_lock); + } + #else + static inline void + topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {} + static inline void + topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {} + static inline void + drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {} + static inline void + drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {} + #define save_mstb_topology_ref(mstb, type) + #define save_port_topology_ref(port, type) + #endif + + static void drm_dp_destroy_mst_branch_device(struct kref *kref) + { + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + + drm_dp_mst_dump_mstb_topology_history(mstb); + + INIT_LIST_HEAD(&mstb->destroy_next); + + /* + * This can get called under mgr->mutex, so we need to perform the + * actual destruction of the mstb in another worker + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); + } + + /** + * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a + * branch device unless it's zero + * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of + * + * Attempts to grab a topology reference to @mstb, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has + * reached 0). Holding a topology reference implies that a malloc reference + * will be held to @mstb as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @mstb. If you already have a topology reference to @mstb, you + * should use drm_dp_mst_topology_get_mstb() instead. + * + * See also: + * drm_dp_mst_topology_get_mstb() + * drm_dp_mst_topology_put_mstb() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ + static int __must_check + drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb) + { + int ret; + + topology_ref_history_lock(mstb->mgr); + ret = kref_get_unless_zero(&mstb->topology_kref); + if (ret) { + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(mstb->mgr); + + return ret; + } + + /** + * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a + * branch device + * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of + * + * Increments &drm_dp_mst_branch.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_put_mstb() + */ + static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb) + { + topology_ref_history_lock(mstb->mgr); + + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + WARN_ON(kref_read(&mstb->topology_kref) == 0); + kref_get(&mstb->topology_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + + topology_ref_history_unlock(mstb->mgr); + } + + /** + * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch + * device + * @mstb: The &struct drm_dp_mst_branch to release the topology reference from + * + * Releases a topology reference from @mstb by decrementing + * &drm_dp_mst_branch.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_get_mstb() + */ + static void + drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb) + { + topology_ref_history_lock(mstb->mgr); + + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(mstb->mgr); + kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device); + } + + static void drm_dp_destroy_port(struct kref *kref) + { + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + + drm_dp_mst_dump_port_topology_history(port); + + /* There's nothing that needs locking to destroy an input port yet */ + if (port->input) { + drm_dp_mst_put_port_malloc(port); + return; + } + + kfree(port->cached_edid); + + /* + * we can't destroy the connector here, as we might be holding the + * mode_config.mutex from an EDID retrieval + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&port->next, &mgr->destroy_port_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); + } + + /** + * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a + * port unless it's zero + * @port: &struct drm_dp_mst_port to increment the topology refcount of + * + * Attempts to grab a topology reference to @port, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached + * 0). Holding a topology reference implies that a malloc reference will be + * held to @port as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @port. If you already have a topology reference to @port, you + * should use drm_dp_mst_topology_get_port() instead. + * + * See also: + * drm_dp_mst_topology_get_port() + * drm_dp_mst_topology_put_port() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ + static int __must_check + drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port) + { + int ret; + + topology_ref_history_lock(port->mgr); + ret = kref_get_unless_zero(&port->topology_kref); + if (ret) { + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(port->mgr); + return ret; + } + + /** + * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port + * @port: The &struct drm_dp_mst_port to increment the topology refcount of + * + * Increments &drm_dp_mst_port.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_put_port() + */ + static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port) + { + topology_ref_history_lock(port->mgr); + + WARN_ON(kref_read(&port->topology_kref) == 0); + kref_get(&port->topology_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + + topology_ref_history_unlock(port->mgr); + } + + /** + * drm_dp_mst_topology_put_port() - release a topology reference to a port + * @port: The &struct drm_dp_mst_port to release the topology reference from + * + * Releases a topology reference from @port by decrementing + * &drm_dp_mst_port.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_get_port() + */ + static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port) + { + topology_ref_history_lock(port->mgr); + + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(port->mgr); + kref_put(&port->topology_kref, drm_dp_destroy_port); + } + + static struct drm_dp_mst_branch * + drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_branch *to_find) + { + struct drm_dp_mst_port *port; + struct drm_dp_mst_branch *rmstb; + + if (to_find == mstb) + return mstb; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->mstb) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + port->mstb, to_find); + if (rmstb) + return rmstb; + } + } + return NULL; + } + + static struct drm_dp_mst_branch * + drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) + { + struct drm_dp_mst_branch *rmstb = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + mgr->mst_primary, mstb); + + if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb)) + rmstb = NULL; + } + mutex_unlock(&mgr->lock); + return rmstb; + } + + static struct drm_dp_mst_port * + drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *to_find) + { + struct drm_dp_mst_port *port, *mport; + + list_for_each_entry(port, &mstb->ports, next) { + if (port == to_find) + return port; + + if (port->mstb) { + mport = drm_dp_mst_topology_get_port_validated_locked( + port->mstb, to_find); + if (mport) + return mport; + } + } + return NULL; + } + + static struct drm_dp_mst_port * + drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) + { + struct drm_dp_mst_port *rport = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rport = drm_dp_mst_topology_get_port_validated_locked( + mgr->mst_primary, port); + + if (rport && !drm_dp_mst_topology_try_get_port(rport)) + rport = NULL; + } + mutex_unlock(&mgr->lock); + return rport; + } + + static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num) + { + struct drm_dp_mst_port *port; + int ret; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + ret = drm_dp_mst_topology_try_get_port(port); + return ret ? port : NULL; + } + } + + return NULL; + } + + /* + * calculate a new RAD for this MST branch device + * if parent has an LCT of 2 then it has 1 nibble of RAD, + * if parent has an LCT of 3 then it has 2 nibbles of RAD, + */ + static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, + u8 *rad) + { + int parent_lct = port->parent->lct; + int shift = 4; + int idx = (parent_lct - 1) / 2; + + if (parent_lct > 1) { + memcpy(rad, port->parent->rad, idx + 1); + shift = (parent_lct % 2) ? 4 : 0; + } else + rad[0] = 0; + + rad[idx] |= port->port_num << shift; + return parent_lct + 1; + } + + static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) + { + switch (pdt) { + case DP_PEER_DEVICE_DP_LEGACY_CONV: + case DP_PEER_DEVICE_SST_SINK: + return true; + case DP_PEER_DEVICE_MST_BRANCHING: + /* For sst branch device */ + if (!mcs) + return true; + + return false; + } + return true; + } + + static int + drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, + bool new_mcs) + { + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + struct drm_dp_mst_branch *mstb; + u8 rad[8], lct; + int ret = 0; + + if (port->pdt == new_pdt && port->mcs == new_mcs) + return 0; + + /* Teardown the old pdt, if there is one */ + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* + * If the new PDT would also have an i2c bus, + * don't bother with reregistering it + */ + if (new_pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(new_pdt, new_mcs)) { + port->pdt = new_pdt; + port->mcs = new_mcs; + return 0; + } + + /* remove i2c over sideband */ + drm_dp_mst_unregister_i2c_bus(port); + } else { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_put_mstb(port->mstb); + port->mstb = NULL; + mutex_unlock(&mgr->lock); + } + } + + port->pdt = new_pdt; + port->mcs = new_mcs; + + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* add i2c over sideband */ + ret = drm_dp_mst_register_i2c_bus(port); + } else { + lct = drm_dp_calculate_rad(port, rad); + mstb = drm_dp_add_mst_branch_device(lct, rad); + if (!mstb) { + ret = -ENOMEM; + drm_err(mgr->dev, "Failed to create MSTB for port %p", port); + goto out; + } + + mutex_lock(&mgr->lock); + port->mstb = mstb; + mstb->mgr = port->mgr; + mstb->port_parent = port; + + /* + * Make sure this port's memory allocation stays + * around until its child MSTB releases it + */ + drm_dp_mst_get_port_malloc(port); + mutex_unlock(&mgr->lock); + + /* And make sure we send a link address for this */ + ret = 1; + } + } + + out: + if (ret < 0) + port->pdt = DP_PEER_DEVICE_NONE; + return ret; + } + + /** + * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_read() does for local + * devices via actual AUX CH. + * + * Return: Number of bytes read, or negative error code on failure. + */ + ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) + { + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_read(port->mgr, port, + offset, size, buffer); + } + + /** + * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_write() does for local + * devices via actual AUX CH. + * + * Return: number of bytes written on success, negative error code on failure. + */ + ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) + { + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_write(port->mgr, port, + offset, size, buffer); + } + + static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) + { + int ret = 0; + + memcpy(mstb->guid, guid, 16); + + if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) { + if (mstb->port_parent) { + ret = drm_dp_send_dpcd_write(mstb->mgr, + mstb->port_parent, + DP_GUID, 16, mstb->guid); + } else { + ret = drm_dp_dpcd_write(mstb->mgr->aux, + DP_GUID, mstb->guid, 16); + } + } + + if (ret < 16 && ret > 0) + return -EPROTO; + + return ret == 16 ? 0 : ret; + } + + static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb, + int pnum, + char *proppath, + size_t proppath_size) + { + int i; + char temp[8]; + + snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id); + for (i = 0; i < (mstb->lct - 1); i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (mstb->rad[i / 2] >> shift) & 0xf; + + snprintf(temp, sizeof(temp), "-%d", port_num); + strlcat(proppath, temp, proppath_size); + } + snprintf(temp, sizeof(temp), "-%d", pnum); + strlcat(proppath, temp, proppath_size); + } + + /** + * drm_dp_mst_connector_late_register() - Late MST connector registration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to register the remote aux device for this MST port. Drivers should + * call this from their mst connector's late_register hook to enable MST aux + * devices. + * + * Return: 0 on success, negative error code on failure. + */ + int drm_dp_mst_connector_late_register(struct drm_connector *connector, + struct drm_dp_mst_port *port) + { + drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + + port->aux.dev = connector->kdev; + return drm_dp_aux_register_devnode(&port->aux); + } + EXPORT_SYMBOL(drm_dp_mst_connector_late_register); + + /** + * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to unregister the remote aux device for this MST port, registered by + * drm_dp_mst_connector_late_register(). Drivers should call this from their mst + * connector's early_unregister hook. + */ + void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, + struct drm_dp_mst_port *port) + { + drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + drm_dp_aux_unregister_devnode(&port->aux); + } + EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); + + static void + drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) + { + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + char proppath[255]; + int ret; + + build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath)); + port->connector = mgr->cbs->add_connector(mgr, port, proppath); + if (!port->connector) { + ret = -ENOMEM; + goto error; + } + + if (port->pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(port->pdt, port->mcs) && + port->port_num >= DP_MST_LOGICAL_PORT_0) + port->cached_edid = drm_get_edid(port->connector, + &port->aux.ddc); + + drm_connector_register(port->connector); + return; + + error: + drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret); + } + + /* + * Drop a topology reference, and unlink the port from the in-memory topology + * layout + */ + static void + drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) + { + mutex_lock(&mgr->lock); + port->parent->num_ports--; + list_del(&port->next); + mutex_unlock(&mgr->lock); + drm_dp_mst_topology_put_port(port); + } + + static struct drm_dp_mst_port * + drm_dp_mst_add_port(struct drm_device *dev, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, u8 port_number) + { + struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL); + + if (!port) + return NULL; + + kref_init(&port->topology_kref); + kref_init(&port->malloc_kref); + port->parent = mstb; + port->port_num = port_number; + port->mgr = mgr; + port->aux.name = "DPMST"; + port->aux.dev = dev->dev; + port->aux.is_remote = true; + + /* initialize the MST downstream port's AUX crc work queue */ + port->aux.drm_dev = dev; + drm_dp_remote_aux_init(&port->aux); + + /* + * Make sure the memory allocation for our parent branch stays + * around until our own memory allocation is released + */ + drm_dp_mst_get_mstb_malloc(mstb); + + return port; + } + + static int + drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, + struct drm_device *dev, + struct drm_dp_link_addr_reply_port *port_msg) + { + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps = 0, ret; + u8 new_pdt = DP_PEER_DEVICE_NONE; + bool new_mcs = 0; + bool created = false, send_link_addr = false, changed = false; + + port = drm_dp_get_port(mstb, port_msg->port_number); + if (!port) { + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + created = true; + changed = true; + } else if (!port->input && port_msg->input_port && port->connector) { + /* Since port->connector can't be changed here, we create a + * new port if input_port changes from 0 to 1 + */ + drm_dp_mst_topology_unlink_port(mgr, port); + drm_dp_mst_topology_put_port(port); + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + changed = true; + created = true; + } else if (port->input && !port_msg->input_port) { + changed = true; + } else if (port->connector) { + /* We're updating a port that's exposed to userspace, so do it + * under lock + */ + drm_modeset_lock(&mgr->base.lock, NULL); + + old_ddps = port->ddps; + changed = port->ddps != port_msg->ddps || + (port->ddps && + (port->ldps != port_msg->legacy_device_plug_status || + port->dpcd_rev != port_msg->dpcd_revision || + port->mcs != port_msg->mcs || + port->pdt != port_msg->peer_device_type || + port->num_sdp_stream_sinks != + port_msg->num_sdp_stream_sinks)); + } + + port->input = port_msg->input_port; + if (!port->input) + new_pdt = port_msg->peer_device_type; + new_mcs = port_msg->mcs; + port->ddps = port_msg->ddps; + port->ldps = port_msg->legacy_device_plug_status; + port->dpcd_rev = port_msg->dpcd_revision; + port->num_sdp_streams = port_msg->num_sdp_streams; + port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks; + + /* manage mstb port lists with mgr lock - take a reference + for this list */ + if (created) { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_get_port(port); + list_add(&port->next, &mstb->ports); + mstb->num_ports++; + mutex_unlock(&mgr->lock); + } + + /* + * Reprobe PBN caps on both hotplug, and when re-probing the link + * for our parent mstb + */ + if (old_ddps != port->ddps || !created) { + if (port->ddps && !port->input) { + ret = drm_dp_send_enum_path_resources(mgr, mstb, + port); + if (ret == 1) + changed = true; + } else { + port->full_pbn = 0; + } + } + + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + send_link_addr = true; + } else if (ret < 0) { + drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); + goto fail; + } + + /* + * If this port wasn't just created, then we're reprobing because + * we're coming out of suspend. In this case, always resend the link + * address if there's an MSTB on this port + */ + if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mcs) + send_link_addr = true; + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (!port->input) + drm_dp_mst_port_add_connector(mstb, port); + + if (send_link_addr && port->mstb) { + ret = drm_dp_send_link_address(mgr, port->mstb); + if (ret == 1) /* MSTB below us changed */ + changed = true; + else if (ret < 0) + goto fail_put; + } + + /* put reference to this port */ + drm_dp_mst_topology_put_port(port); + return changed; + + fail: + drm_dp_mst_topology_unlink_port(mgr, port); + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + fail_put: + drm_dp_mst_topology_put_port(port); + return ret; + } + + static void + drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, + struct drm_dp_connection_status_notify *conn_stat) + { + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps, ret; + u8 new_pdt; + bool new_mcs; + bool dowork = false, create_connector = false; + + port = drm_dp_get_port(mstb, conn_stat->port_number); + if (!port) + return; + + if (port->connector) { + if (!port->input && conn_stat->input_port) { + /* + * We can't remove a connector from an already exposed + * port, so just throw the port out and make sure we + * reprobe the link address of it's parent MSTB + */ + drm_dp_mst_topology_unlink_port(mgr, port); + mstb->link_address_sent = false; + dowork = true; + goto out; + } + + /* Locking is only needed if the port's exposed to userspace */ + drm_modeset_lock(&mgr->base.lock, NULL); + } else if (port->input && !conn_stat->input_port) { + create_connector = true; + /* Reprobe link address so we get num_sdp_streams */ + mstb->link_address_sent = false; + dowork = true; + } + + old_ddps = port->ddps; + port->input = conn_stat->input_port; + port->ldps = conn_stat->legacy_device_plug_status; + port->ddps = conn_stat->displayport_device_plug_status; + + if (old_ddps != port->ddps) { + if (port->ddps && !port->input) + drm_dp_send_enum_path_resources(mgr, mstb, port); + else + port->full_pbn = 0; + } + + new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; + new_mcs = conn_stat->message_capability_status; + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + dowork = true; + } else if (ret < 0) { + drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret); + dowork = false; + } + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (create_connector) + drm_dp_mst_port_add_connector(mstb, port); + + out: + drm_dp_mst_topology_put_port(port); + if (dowork) + queue_work(system_long_wq, &mstb->mgr->work); + } + + static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr, + u8 lct, u8 *rad) + { + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_port *port; + int i, ret; + /* find the port by iterating down */ + + mutex_lock(&mgr->lock); + mstb = mgr->mst_primary; + + if (!mstb) + goto out; + + for (i = 0; i < lct - 1; i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (rad[i / 2] >> shift) & 0xf; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + mstb = port->mstb; + if (!mstb) { + drm_err(mgr->dev, + "failed to lookup MSTB with lct %d, rad %02x\n", + lct, rad[0]); + goto out; + } + + break; + } + } + } + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + out: + mutex_unlock(&mgr->lock); + return mstb; + } + + static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper( + struct drm_dp_mst_branch *mstb, + const uint8_t *guid) + { + struct drm_dp_mst_branch *found_mstb; + struct drm_dp_mst_port *port; + + if (memcmp(mstb->guid, guid, 16) == 0) + return mstb; + + + list_for_each_entry(port, &mstb->ports, next) { + if (!port->mstb) + continue; + + found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid); + + if (found_mstb) + return found_mstb; + } + + return NULL; + } + + static struct drm_dp_mst_branch * + drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr, + const uint8_t *guid) + { + struct drm_dp_mst_branch *mstb; + int ret; + + /* find the port by iterating down */ + mutex_lock(&mgr->lock); + + mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid); + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + + mutex_unlock(&mgr->lock); + return mstb; + } + + static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) + { + struct drm_dp_mst_port *port; + int ret; + bool changed = false; + + if (!mstb->link_address_sent) { + ret = drm_dp_send_link_address(mgr, mstb); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + + list_for_each_entry(port, &mstb->ports, next) { + struct drm_dp_mst_branch *mstb_child = NULL; + + if (port->input || !port->ddps) + continue; + + if (port->mstb) + mstb_child = drm_dp_mst_topology_get_mstb_validated( + mgr, port->mstb); + + if (mstb_child) { + ret = drm_dp_check_and_send_link_address(mgr, + mstb_child); + drm_dp_mst_topology_put_mstb(mstb_child); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + } + + return changed; + } + + static void drm_dp_mst_link_probe_work(struct work_struct *work) + { + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, work); + struct drm_device *dev = mgr->dev; + struct drm_dp_mst_branch *mstb; + int ret; + bool clear_payload_id_table; + + mutex_lock(&mgr->probe_lock); + + mutex_lock(&mgr->lock); + clear_payload_id_table = !mgr->payload_id_table_cleared; + mgr->payload_id_table_cleared = true; + + mstb = mgr->mst_primary; + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + mutex_unlock(&mgr->lock); + if (!mstb) { + mutex_unlock(&mgr->probe_lock); + return; + } + + /* + * Certain branch devices seem to incorrectly report an available_pbn + * of 0 on downstream sinks, even after clearing the + * DP_PAYLOAD_ALLOCATE_* registers in + * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C + * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make + * things work again. + */ + if (clear_payload_id_table) { + drm_dbg_kms(dev, "Clearing payload ID table\n"); + drm_dp_send_clear_payload_id_table(mgr, mstb); + } + + ret = drm_dp_check_and_send_link_address(mgr, mstb); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_unlock(&mgr->probe_lock); + if (ret > 0) + drm_kms_helper_hotplug_event(dev); + } + + static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid) + { + u64 salt; + + if (memchr_inv(guid, 0, 16)) + return true; + + salt = get_jiffies_64(); + + memcpy(&guid[0], &salt, sizeof(u64)); + memcpy(&guid[8], &salt, sizeof(u64)); + + return false; + } + + static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes) + { + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_READ; + req.u.dpcd_read.port_number = port_num; + req.u.dpcd_read.dpcd_address = offset; + req.u.dpcd_read.num_bytes = num_bytes; + drm_dp_encode_sideband_req(&req, msg); + } + + static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, + bool up, u8 *msg, int len) + { + int ret; + int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; + int tosend, total, offset; + int retries = 0; + + retry: + total = len; + offset = 0; + do { + tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total); + + ret = drm_dp_dpcd_write(mgr->aux, regbase + offset, + &msg[offset], + tosend); + if (ret != tosend) { + if (ret == -EIO && retries < 5) { + retries++; + goto retry; + } + drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret); + + return -EIO; + } + offset += tosend; + total -= tosend; + } while (total > 0); + return 0; + } + + static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr, + struct drm_dp_sideband_msg_tx *txmsg) + { + struct drm_dp_mst_branch *mstb = txmsg->dst; + u8 req_type; + + req_type = txmsg->msg[0] & 0x7f; + if (req_type == DP_CONNECTION_STATUS_NOTIFY || + req_type == DP_RESOURCE_STATUS_NOTIFY || + req_type == DP_CLEAR_PAYLOAD_ID_TABLE) + hdr->broadcast = 1; + else + hdr->broadcast = 0; + hdr->path_msg = txmsg->path_msg; + if (hdr->broadcast) { + hdr->lct = 1; + hdr->lcr = 6; + } else { + hdr->lct = mstb->lct; + hdr->lcr = mstb->lct - 1; + } + + memcpy(hdr->rad, mstb->rad, hdr->lct / 2); + + return 0; + } + /* + * process a single block of the next message in the sideband queue + */ + static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg, + bool up) + { + u8 chunk[48]; + struct drm_dp_sideband_msg_hdr hdr; + int len, space, idx, tosend; + int ret; + + if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + return 0; + + memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr)); + + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) + txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND; + + /* make hdr from dst mst */ + ret = set_hdr_from_dst_qlock(&hdr, txmsg); + if (ret < 0) + return ret; + + /* amount left to send in this message */ + len = txmsg->cur_len - txmsg->cur_offset; + + /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */ + space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr); + + tosend = min(len, space); + if (len == txmsg->cur_len) + hdr.somt = 1; + if (space >= len) + hdr.eomt = 1; + + + hdr.msg_len = tosend + 1; + drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx); + memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend); + /* add crc at end */ + drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend); + idx += tosend + 1; + + ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); + if (ret) { + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_printf(&p, "sideband msg failed to send\n"); + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + return ret; + } + + txmsg->cur_offset += tosend; + if (txmsg->cur_offset == txmsg->cur_len) { + txmsg->state = DRM_DP_SIDEBAND_TX_SENT; + return 1; + } + return 0; + } + + static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) + { + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + WARN_ON(!mutex_is_locked(&mgr->qlock)); + + /* construct a chunk from the first msg in the tx_msg queue */ + if (list_empty(&mgr->tx_msg_downq)) + return; + + txmsg = list_first_entry(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + ret = process_single_tx_qlock(mgr, txmsg, false); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret); + list_del(&txmsg->next); + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + wake_up_all(&mgr->tx_waitq); + } + } + + static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) + { + mutex_lock(&mgr->qlock); + list_add_tail(&txmsg->next, &mgr->tx_msg_downq); + + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + + if (list_is_singular(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); + } + + static void + drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_link_address_ack_reply *reply) + { + struct drm_dp_link_addr_reply_port *port_reply; + int i; + + for (i = 0; i < reply->nports; i++) { + port_reply = &reply->ports[i]; + drm_dbg_kms(mgr->dev, + "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n", + i, + port_reply->input_port, + port_reply->peer_device_type, + port_reply->port_number, + port_reply->dpcd_revision, + port_reply->mcs, + port_reply->ddps, + port_reply->legacy_device_plug_status, + port_reply->num_sdp_streams, + port_reply->num_sdp_stream_sinks); + } + } + + static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) + { + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_link_address_ack_reply *reply; + struct drm_dp_mst_port *port, *tmp; + int i, ret, port_mask = 0; + bool changed = false; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_link_address(txmsg); + + mstb->link_address_sent = true; + drm_dp_queue_down_tx(mgr, txmsg); + + /* FIXME: Actually do some real error handling here */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret <= 0) { + drm_err(mgr->dev, "Sending link address failed with %d\n", ret); + goto out; + } + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_err(mgr->dev, "link address NAK received\n"); + ret = -EIO; + goto out; + } + + reply = &txmsg->reply.u.link_addr; + drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports); + drm_dp_dump_link_address(mgr, reply); + + ret = drm_dp_check_mstb_guid(mstb, reply->guid); + if (ret) { + char buf[64]; + + drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf)); + drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret); + goto out; + } + + for (i = 0; i < reply->nports; i++) { + port_mask |= BIT(reply->ports[i].port_number); + ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev, + &reply->ports[i]); + if (ret == 1) + changed = true; + else if (ret < 0) + goto out; + } + + /* Prune any ports that are currently a part of mstb in our in-memory + * topology, but were not seen in this link address. Usually this + * means that they were removed while the topology was out of sync, + * e.g. during suspend/resume + */ + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, tmp, &mstb->ports, next) { + if (port_mask & BIT(port->port_num)) + continue; + + drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n", + port->port_num); + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + changed = true; + } + mutex_unlock(&mgr->lock); + + out: + if (ret <= 0) + mstb->link_address_sent = false; + kfree(txmsg); + return ret < 0 ? ret : changed; + } + + static void + drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) + { + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return; + + txmsg->dst = mstb; + build_clear_payload_id_table(txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + drm_dbg_kms(mgr->dev, "clear payload table id nak received\n"); + + kfree(txmsg); + } + + static int + drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) + { + struct drm_dp_enum_path_resources_ack_reply *path_res; + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_enum_path_resources(txmsg, port->port_num); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + ret = 0; + path_res = &txmsg->reply.u.path_resources; + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "enum path resources nak received\n"); + } else { + if (port->port_num != path_res->port_number) + DRM_ERROR("got incorrect port in response\n"); + + drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n", + path_res->port_number, + path_res->full_payload_bw_number, + path_res->avail_payload_bw_number); + + /* + * If something changed, make sure we send a + * hotplug + */ + if (port->full_pbn != path_res->full_payload_bw_number || + port->fec_capable != path_res->fec_capable) + ret = 1; + + port->full_pbn = path_res->full_payload_bw_number; + port->fec_capable = path_res->fec_capable; + } + } + + kfree(txmsg); + return ret; + } + + static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) + { + if (!mstb->port_parent) + return NULL; + + if (mstb->port_parent->mstb != mstb) + return mstb->port_parent; + + return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent); + } + + /* + * Searches upwards in the topology starting from mstb to try to find the + * closest available parent of mstb that's still connected to the rest of the + * topology. This can be used in order to perform operations like releasing + * payloads, where the branch device which owned the payload may no longer be + * around and thus would require that the payload on the last living relative + * be freed instead. + */ + static struct drm_dp_mst_branch * + drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int *port_num) + { + struct drm_dp_mst_branch *rmstb = NULL; + struct drm_dp_mst_port *found_port; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out; + + do { + found_port = drm_dp_get_last_connected_port_to_mstb(mstb); + if (!found_port) + break; + + if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) { + rmstb = found_port->parent; + *port_num = found_port->port_num; + } else { + /* Search again, starting from this parent */ + mstb = found_port->parent; + } + } while (!rmstb); + out: + mutex_unlock(&mgr->lock); + return rmstb; + } + + static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + int pbn) + { + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + int ret, port_num; + u8 sinks[DRM_DP_MAX_SDP_STREAMS]; + int i; + + port_num = port->port_num; + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) { + mstb = drm_dp_get_last_connected_port_and_mstb(mgr, + port->parent, + &port_num); + + if (!mstb) + return -EINVAL; + } + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + for (i = 0; i < port->num_sdp_streams; i++) + sinks[i] = i; + + txmsg->dst = mstb; + build_allocate_payload(txmsg, port_num, + id, + pbn, port->num_sdp_streams, sinks); + + drm_dp_queue_down_tx(mgr, txmsg); + + /* + * FIXME: there is a small chance that between getting the last + * connected mstb and sending the payload message, the last connected + * mstb could also be removed from the topology. In the future, this + * needs to be fixed by restarting the + * drm_dp_get_last_connected_port_and_mstb() search in the event of a + * timeout if the topology is still connected to the system. + */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); + fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; + } + + int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, bool power_up) + { + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + drm_dp_mst_topology_put_port(port); + return -ENOMEM; + } + + txmsg->dst = port->parent; + build_power_updown_phy(txmsg, port->port_num, power_up); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); + drm_dp_mst_topology_put_port(port); + + return ret; + } + EXPORT_SYMBOL(drm_dp_send_power_updown_phy); + + int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + struct drm_dp_query_stream_enc_status_ack_reply *status) + { + struct drm_dp_sideband_msg_tx *txmsg; + u8 nonce[7]; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) { + ret = -EINVAL; + goto out_get_port; + } + + get_random_bytes(nonce, sizeof(nonce)); + + /* + * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message + * transaction at the MST Branch device directly connected to the + * Source" + */ + txmsg->dst = mgr->mst_primary; + + build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg); + if (ret < 0) { + goto out; + } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "query encryption status nak received\n"); + ret = -ENXIO; + goto out; + } + + ret = 0; + memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status)); + + out: + drm_dp_mst_topology_put_port(port); + out_get_port: + kfree(txmsg); + return ret; + } + EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status); + + static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) + { + int ret; + + ret = drm_dp_dpcd_write_payload(mgr, id, payload); + if (ret < 0) { + payload->payload_state = 0; + return ret; + } + payload->payload_state = DP_PAYLOAD_LOCAL; + return 0; + } + + static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) + { + int ret; + + ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn); + if (ret < 0) + return ret; + payload->payload_state = DP_PAYLOAD_REMOTE; + return ret; + } + + static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) + { + drm_dbg_kms(mgr->dev, "\n"); + /* it's okay for these to fail */ + if (port) { + drm_dp_payload_send_msg(mgr, port, id, 0); + } + + drm_dp_dpcd_write_payload(mgr, id, payload); + payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; + return 0; + } + + static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) + { + payload->payload_state = 0; + return 0; + } + + /** + * drm_dp_update_payload_part1() - Execute payload update part 1 + * @mgr: manager to use. + * @start_slot: this is the cur slot + * + * NOTE: start_slot is a temporary workaround for non-atomic drivers, + * this will be removed when non-atomic mst helpers are moved out of the helper + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step just writes the VCPI to the MST device. For slots->0 + * transitions, this writes the updated VCPIs and removes the + * remote VC payloads. + * + * after calling this the driver should generate ACT and payload + * packets. + */ + int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot) + { + struct drm_dp_payload req_payload; + struct drm_dp_mst_port *port; + int i, j; + int cur_slots = start_slot; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i]; + struct drm_dp_payload *payload = &mgr->payloads[i]; + bool put_port = false; + + /* solve the current payloads - compare to the hw ones + - update the hw view */ + req_payload.start_slot = cur_slots; + if (vcpi) { + port = container_of(vcpi, struct drm_dp_mst_port, + vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) { + drm_dbg_kms(mgr->dev, + "Virtual channel %d is not in current topology\n", + i); + continue; + } + /* Validated ports don't matter if we're releasing + * VCPI + */ + if (vcpi->num_slots) { + port = drm_dp_mst_topology_get_port_validated( + mgr, port); + if (!port) { + if (vcpi->num_slots == payload->num_slots) { + cur_slots += vcpi->num_slots; + payload->start_slot = req_payload.start_slot; + continue; + } else { + drm_dbg_kms(mgr->dev, + "Fail:set payload to invalid sink"); + mutex_unlock(&mgr->payload_lock); + return -EINVAL; + } + } + put_port = true; + } + + req_payload.num_slots = vcpi->num_slots; + req_payload.vcpi = vcpi->vcpi; + } else { + port = NULL; + req_payload.num_slots = 0; + } + + payload->start_slot = req_payload.start_slot; + /* work out what is required to happen with this payload */ + if (payload->num_slots != req_payload.num_slots) { + + /* need to push an update for this payload */ + if (req_payload.num_slots) { + drm_dp_create_payload_step1(mgr, vcpi->vcpi, + &req_payload); + payload->num_slots = req_payload.num_slots; + payload->vcpi = req_payload.vcpi; + + } else if (payload->num_slots) { + payload->num_slots = 0; + drm_dp_destroy_payload_step1(mgr, port, + payload->vcpi, + payload); + req_payload.payload_state = + payload->payload_state; + payload->start_slot = 0; + } + payload->payload_state = req_payload.payload_state; + } + cur_slots += req_payload.num_slots; + + if (put_port) + drm_dp_mst_topology_put_port(port); + } + + for (i = 0; i < mgr->max_payloads; /* do nothing */) { + if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) { + i++; + continue; + } + + drm_dbg_kms(mgr->dev, "removing payload %d\n", i); + for (j = i; j < mgr->max_payloads - 1; j++) { + mgr->payloads[j] = mgr->payloads[j + 1]; + mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; + + if (mgr->proposed_vcpis[j] && + mgr->proposed_vcpis[j]->num_slots) { + set_bit(j + 1, &mgr->payload_mask); + } else { + clear_bit(j + 1, &mgr->payload_mask); + } + } + + memset(&mgr->payloads[mgr->max_payloads - 1], 0, + sizeof(struct drm_dp_payload)); + mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; + clear_bit(mgr->max_payloads, &mgr->payload_mask); + } + mutex_unlock(&mgr->payload_lock); + + return 0; + } + EXPORT_SYMBOL(drm_dp_update_payload_part1); + + /** + * drm_dp_update_payload_part2() - Execute payload update part 2 + * @mgr: manager to use. + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step writes the remote VC payload commands. For slots->0 + * this just resets some internal state. + */ + int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) + { + struct drm_dp_mst_port *port; + int i; + int ret = 0; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + + if (!mgr->proposed_vcpis[i]) + continue; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + continue; + + drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state); + if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { + ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { + ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } + if (ret) { + mutex_unlock(&mgr->payload_lock); + return ret; + } + } + mutex_unlock(&mgr->payload_lock); + return 0; + } + EXPORT_SYMBOL(drm_dp_update_payload_part2); + + static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) + { + int ret = 0; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_read(txmsg, port->port_num, offset, size); + txmsg->dst = port->parent; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret < 0) + goto fail_free; + + /* DPCD read should never be NACKed */ + if (txmsg->reply.reply_type == 1) { + drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", + mstb, port->port_num, offset, size); + ret = -EIO; + goto fail_free; + } + + if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { + ret = -EPROTO; + goto fail_free; + } + + ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, + size); + memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); + + fail_free: + kfree(txmsg); + fail_put: + drm_dp_mst_topology_put_mstb(mstb); + + return ret; + } + + static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) + { + int ret; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_write(txmsg, port->port_num, offset, size, bytes); + txmsg->dst = mstb; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EIO; + else + ret = size; + } + + kfree(txmsg); + fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; + } + + static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type) + { + struct drm_dp_sideband_msg_reply_body reply; + + reply.reply_type = DP_SIDEBAND_REPLY_ACK; + reply.req_type = req_type; + drm_dp_encode_sideband_reply(&reply, msg); + return 0; + } + + static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int req_type, bool broadcast) + { + struct drm_dp_sideband_msg_tx *txmsg; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + drm_dp_encode_up_ack_reply(txmsg, req_type); + + mutex_lock(&mgr->qlock); + /* construct a chunk from the first msg in the tx_msg queue */ + process_single_tx_qlock(mgr, txmsg, true); + mutex_unlock(&mgr->qlock); + + kfree(txmsg); + return 0; + } + + /** + * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link + * @mgr: The &drm_dp_mst_topology_mgr to use + * @link_rate: link rate in 10kbits/s units + * @link_lane_count: lane count + * + * Calculate the total bandwidth of a MultiStream Transport link. The returned + * value is in units of PBNs/(timeslots/1 MTP). This value can be used to + * convert the number of PBNs required for a given stream to the number of + * timeslots this stream requires in each MTP. + */ + int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr, + int link_rate, int link_lane_count) + { + if (link_rate == 0 || link_lane_count == 0) + drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n", + link_rate, link_lane_count); + + /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */ + return link_rate * link_lane_count / 54000; + } + EXPORT_SYMBOL(drm_dp_get_vc_payload_bw); + + /** + * drm_dp_read_mst_cap() - check whether or not a sink supports MST + * @aux: The DP AUX channel to use + * @dpcd: A cached copy of the DPCD capabilities for this sink + * + * Returns: %True if the sink supports MST, %false otherwise + */ + bool drm_dp_read_mst_cap(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + u8 mstm_cap; + + if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) + return false; + + if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1) + return false; + + return mstm_cap & DP_MST_CAP; + } + EXPORT_SYMBOL(drm_dp_read_mst_cap); + + /** + * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager + * @mgr: manager to set state for + * @mst_state: true to enable MST on this connector - false to disable. + * + * This is called by the driver when it detects an MST capable device plugged + * into a DP MST capable port, or when a DP MST capable device is unplugged. + */ + int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state) + { + int ret = 0; + struct drm_dp_mst_branch *mstb = NULL; + + mutex_lock(&mgr->payload_lock); + mutex_lock(&mgr->lock); + if (mst_state == mgr->mst_state) + goto out_unlock; + + mgr->mst_state = mst_state; + /* set the device into MST mode */ + if (mst_state) { + struct drm_dp_payload reset_pay; + int lane_count; + int link_rate; + + WARN_ON(mgr->mst_primary); + + /* get dpcd info */ + ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n", + mgr->aux->name, ret); + goto out_unlock; + } + + lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count); + link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate); + mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr, + link_rate, + lane_count); + if (mgr->pbn_div == 0) { + ret = -EINVAL; + goto out_unlock; + } + + /* add initial branch device at LCT 1 */ + mstb = drm_dp_add_mst_branch_device(1, NULL); + if (mstb == NULL) { + ret = -ENOMEM; + goto out_unlock; + } + mstb->mgr = mgr; + + /* give this the main reference */ + mgr->mst_primary = mstb; + drm_dp_mst_topology_get_mstb(mgr->mst_primary); + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) + goto out_unlock; + + reset_pay.start_slot = 0; + reset_pay.num_slots = 0x3f; + drm_dp_dpcd_write_payload(mgr, 0, &reset_pay); + + queue_work(system_long_wq, &mgr->work); + + ret = 0; + } else { + /* disable MST on the device */ + mstb = mgr->mst_primary; + mgr->mst_primary = NULL; + /* this can fail if the device is gone */ + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0); + ret = 0; + memset(mgr->payloads, 0, + mgr->max_payloads * sizeof(mgr->payloads[0])); + memset(mgr->proposed_vcpis, 0, + mgr->max_payloads * sizeof(mgr->proposed_vcpis[0])); + mgr->payload_mask = 0; + set_bit(0, &mgr->payload_mask); + mgr->vcpi_mask = 0; + mgr->payload_id_table_cleared = false; + } + + out_unlock: + mutex_unlock(&mgr->lock); + mutex_unlock(&mgr->payload_lock); + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + return ret; + + } + EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst); + + static void + drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) + { + struct drm_dp_mst_port *port; + + /* The link address will need to be re-sent on resume */ + mstb->link_address_sent = false; + + list_for_each_entry(port, &mstb->ports, next) + if (port->mstb) + drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); + } + + /** + * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager + * @mgr: manager to suspend + * + * This function tells the MST device that we can't handle UP messages + * anymore. This should stop it from sending any since we are suspended. + */ + void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr) + { + mutex_lock(&mgr->lock); + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | DP_UPSTREAM_IS_SRC); + mutex_unlock(&mgr->lock); + flush_work(&mgr->up_req_work); + flush_work(&mgr->work); + flush_work(&mgr->delayed_destroy_work); + + mutex_lock(&mgr->lock); + if (mgr->mst_state && mgr->mst_primary) + drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary); + mutex_unlock(&mgr->lock); + } + EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend); + + /** + * drm_dp_mst_topology_mgr_resume() - resume the MST manager + * @mgr: manager to resume + * @sync: whether or not to perform topology reprobing synchronously + * + * This will fetch DPCD and see if the device is still there, + * if it is, it will rewrite the MSTM control bits, and return. + * + * If the device fails this returns -1, and the driver should do + * a full MST reprobe, in case we were undocked. + * + * During system resume (where it is assumed that the driver will be calling + * drm_atomic_helper_resume()) this function should be called beforehand with + * @sync set to true. In contexts like runtime resume where the driver is not + * expected to be calling drm_atomic_helper_resume(), this function should be + * called with @sync set to false in order to avoid deadlocking. + * + * Returns: -1 if the MST topology was removed while we were suspended, 0 + * otherwise. + */ + int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, + bool sync) + { + int ret; + u8 guid[16]; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out_fail; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, + DP_RECEIVER_CAP_SIZE); + if (ret != DP_RECEIVER_CAP_SIZE) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); + goto out_fail; + } + + /* Some hubs forget their guids after they resume */ + ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); + if (ret != 16) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid); + if (ret) { + drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); + goto out_fail; + } + + /* + * For the final step of resuming the topology, we need to bring the + * state of our in-memory topology back into sync with reality. So, + * restart the probing process as if we're probing a new hub + */ + queue_work(system_long_wq, &mgr->work); + mutex_unlock(&mgr->lock); + + if (sync) { + drm_dbg_kms(mgr->dev, + "Waiting for link probe work to finish re-syncing topology...\n"); + flush_work(&mgr->work); + } + + return 0; + + out_fail: + mutex_unlock(&mgr->lock); + return -1; + } + EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume); + + static bool + drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, + struct drm_dp_mst_branch **mstb) + { + int len; + u8 replyblock[32]; + int replylen, curreply; + int ret; + u8 hdrlen; + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_rx *msg = + up ? &mgr->up_req_recv : &mgr->down_rep_recv; + int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE : + DP_SIDEBAND_MSG_DOWN_REP_BASE; + + if (!up) + *mstb = NULL; + + len = min(mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret); + return false; + } + + ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen); + if (ret == false) { + print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16, + 1, replyblock, len, false); + drm_dbg_kms(mgr->dev, "ERROR: failed header\n"); + return false; + } + + if (!up) { + /* Caller is responsible for giving back this reference */ + *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); + if (!*mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); + return false; + } + } + + if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) { + drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); + return false; + } + + replylen = min(msg->curchunk_len, (u8)(len - hdrlen)); + ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen); + if (!ret) { + drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]); + return false; + } + + replylen = msg->curchunk_len + msg->curchunk_hdrlen - len; + curreply = len; + while (replylen > 0) { + len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply, + replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n", + len, ret); + return false; + } + + ret = drm_dp_sideband_append_payload(msg, replyblock, len); + if (!ret) { + drm_dbg_kms(mgr->dev, "failed to build sideband msg\n"); + return false; + } + + curreply += len; + replylen -= len; + } + return true; + } + + static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) + { + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv; + + if (!drm_dp_get_one_sb_msg(mgr, false, &mstb)) + goto out; + + /* Multi-packet message transmission, don't clear the reply */ + if (!msg->have_eomt) + goto out; + + /* find the message */ + mutex_lock(&mgr->qlock); + txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + mutex_unlock(&mgr->qlock); + + /* Were we actually expecting a response, and from this mstb? */ + if (!txmsg || txmsg->dst != mstb) { + struct drm_dp_sideband_msg_hdr *hdr; + + hdr = &msg->initial_hdr; + drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n", + mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]); + goto out_clear_reply; + } + + drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply); + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, + "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n", + txmsg->reply.req_type, + drm_dp_mst_req_type_str(txmsg->reply.req_type), + txmsg->reply.u.nak.reason, + drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason), + txmsg->reply.u.nak.nak_data); + } + + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_lock(&mgr->qlock); + txmsg->state = DRM_DP_SIDEBAND_TX_RX; + list_del(&txmsg->next); + mutex_unlock(&mgr->qlock); + + wake_up_all(&mgr->tx_waitq); + + return 0; + + out_clear_reply: + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); + out: + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + + return 0; + } + + static inline bool + drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_pending_up_req *up_req) + { + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_req_body *msg = &up_req->msg; + struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr; + bool hotplug = false; + + if (hdr->broadcast) { + const u8 *guid = NULL; + + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) + guid = msg->u.conn_stat.guid; + else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY) + guid = msg->u.resource_stat.guid; + + if (guid) + mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid); + } else { + mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad); + } + + if (!mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct); + return false; + } + + /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */ + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) { + drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat); + hotplug = true; + } + + drm_dp_mst_topology_put_mstb(mstb); + return hotplug; + } + + static void drm_dp_mst_up_req_work(struct work_struct *work) + { + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + up_req_work); + struct drm_dp_pending_up_req *up_req; + bool send_hotplug = false; + + mutex_lock(&mgr->probe_lock); + while (true) { + mutex_lock(&mgr->up_req_lock); + up_req = list_first_entry_or_null(&mgr->up_req_list, + struct drm_dp_pending_up_req, + next); + if (up_req) + list_del(&up_req->next); + mutex_unlock(&mgr->up_req_lock); + + if (!up_req) + break; + + send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req); + kfree(up_req); + } + mutex_unlock(&mgr->probe_lock); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); + } + + static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr) + { + struct drm_dp_pending_up_req *up_req; + + if (!drm_dp_get_one_sb_msg(mgr, true, NULL)) + goto out; + + if (!mgr->up_req_recv.have_eomt) + return 0; + + up_req = kzalloc(sizeof(*up_req), GFP_KERNEL); + if (!up_req) + return -ENOMEM; + + INIT_LIST_HEAD(&up_req->next); + + drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg); + + if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY && + up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) { + drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n", + up_req->msg.req_type); + kfree(up_req); + goto out; + } + + drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type, + false); + + if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) { + const struct drm_dp_connection_status_notify *conn_stat = + &up_req->msg.u.conn_stat; + + drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", + conn_stat->port_number, + conn_stat->legacy_device_plug_status, + conn_stat->displayport_device_plug_status, + conn_stat->message_capability_status, + conn_stat->input_port, + conn_stat->peer_device_type); + } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) { + const struct drm_dp_resource_status_notify *res_stat = + &up_req->msg.u.resource_stat; + + drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n", + res_stat->port_number, + res_stat->available_pbn); + } + + up_req->hdr = mgr->up_req_recv.initial_hdr; + mutex_lock(&mgr->up_req_lock); + list_add_tail(&up_req->next, &mgr->up_req_list); + mutex_unlock(&mgr->up_req_lock); + queue_work(system_long_wq, &mgr->up_req_work); + + out: + memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx)); + return 0; + } + + /** + * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify + * @mgr: manager to notify irq for. + * @esi: 4 bytes from SINK_COUNT_ESI + * @handled: whether the hpd interrupt was consumed or not + * + * This should be called from the driver when it detects a short IRQ, + * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The + * topology manager will process the sideband messages received as a result + * of this. + */ + int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled) + { + int ret = 0; + int sc; + *handled = false; + sc = DP_GET_SINK_COUNT(esi[0]); + + if (sc != mgr->sink_count) { + mgr->sink_count = sc; + *handled = true; + } + + if (esi[1] & DP_DOWN_REP_MSG_RDY) { + ret = drm_dp_mst_handle_down_rep(mgr); + *handled = true; + } + + if (esi[1] & DP_UP_REQ_MSG_RDY) { + ret |= drm_dp_mst_handle_up_req(mgr); + *handled = true; + } + + drm_dp_mst_kick_tx(mgr); + return ret; + } + EXPORT_SYMBOL(drm_dp_mst_hpd_irq); + + /** + * drm_dp_mst_detect_port() - get connection status for an MST port + * @connector: DRM connector for this port + * @ctx: The acquisition context to use for grabbing locks + * @mgr: manager for this port + * @port: pointer to a port + * + * This returns the current connection state for a port. + */ + int + drm_dp_mst_detect_port(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) + { + int ret; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return connector_status_disconnected; + + ret = drm_modeset_lock(&mgr->base.lock, ctx); + if (ret) + goto out; + + ret = connector_status_disconnected; + + if (!port->ddps) + goto out; + + switch (port->pdt) { + case DP_PEER_DEVICE_NONE: + break; + case DP_PEER_DEVICE_MST_BRANCHING: + if (!port->mcs) + ret = connector_status_connected; + break; + + case DP_PEER_DEVICE_SST_SINK: + ret = connector_status_connected; + /* for logical ports - cache the EDID */ + if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid) + port->cached_edid = drm_get_edid(connector, &port->aux.ddc); + break; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + if (port->ldps) + ret = connector_status_connected; + break; + } + out: + drm_dp_mst_topology_put_port(port); + return ret; + } + EXPORT_SYMBOL(drm_dp_mst_detect_port); + + /** + * drm_dp_mst_get_edid() - get EDID for an MST port + * @connector: toplevel connector to get EDID for + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This returns an EDID for the port connected to a connector, + * It validates the pointer still exists so the caller doesn't require a + * reference. + */ + struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) + { + struct edid *edid = NULL; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return NULL; + + if (port->cached_edid) + edid = drm_edid_duplicate(port->cached_edid); + else { + edid = drm_get_edid(connector, &port->aux.ddc); + } + port->has_audio = drm_detect_monitor_audio(edid); + drm_dp_mst_topology_put_port(port); + return edid; + } + EXPORT_SYMBOL(drm_dp_mst_get_edid); + + /** + * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value + * @mgr: manager to use + * @pbn: payload bandwidth to convert into slots. + * + * Calculate the number of VCPI slots that will be required for the given PBN + * value. This function is deprecated, and should not be used in atomic + * drivers. + * + * RETURNS: + * The total slots required for this port, or error. + */ + int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, + int pbn) + { + int num_slots; + + num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); + + /* max. time slots - one slot for MTP header */ + if (num_slots > 63) + return -ENOSPC; + return num_slots; + } + EXPORT_SYMBOL(drm_dp_find_vcpi_slots); + + static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi, int pbn, int slots) + { + int ret; + + vcpi->pbn = pbn; + vcpi->aligned_pbn = slots * mgr->pbn_div; + vcpi->num_slots = slots; + + ret = drm_dp_mst_assign_payload_id(mgr, vcpi); + if (ret < 0) + return ret; + return 0; + } + + /** + * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: port to find vcpi slots for + * @pbn: bandwidth required for the mode in PBN + * @pbn_div: divider for DSC mode that takes FEC into account + * + * Allocates VCPI slots to @port, replacing any previous VCPI allocations it + * may have had. Any atomic drivers which support MST must call this function + * in their &drm_encoder_helper_funcs.atomic_check() callback to change the + * current VCPI allocation for the new state, but only when + * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set + * to ensure compatibility with userspace applications that still use the + * legacy modesetting UAPI. + * + * Allocations set by this function are not checked against the bandwidth + * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). + * + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase. + * + * See also: + * drm_dp_atomic_release_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * Total slots in the atomic state assigned for this port, or a negative error + * code if the port no longer exists + */ + int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, + int pbn_div) + { + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos, *vcpi = NULL; + int prev_slots, prev_bw, req_slots; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + /* Find the current allocation for this port, if any */ + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + vcpi = pos; + prev_slots = vcpi->vcpi; + prev_bw = vcpi->pbn; + + /* + * This should never happen, unless the driver tries + * releasing and allocating the same VCPI allocation, + * which is an error + */ + if (WARN_ON(!prev_slots)) { + drm_err(mgr->dev, + "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n", + port); + return -EINVAL; + } + + break; + } + } + if (!vcpi) { + prev_slots = 0; + prev_bw = 0; + } + + if (pbn_div <= 0) + pbn_div = mgr->pbn_div; + + req_slots = DIV_ROUND_UP(pbn, pbn_div); + + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_slots, req_slots); + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_bw, pbn); + + /* Add the new allocation to the state */ + if (!vcpi) { + vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + return -ENOMEM; + + drm_dp_mst_get_port_malloc(port); + vcpi->port = port; + list_add(&vcpi->next, &topology_state->vcpis); + } + vcpi->vcpi = req_slots; + vcpi->pbn = pbn; + + return req_slots; + } + EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); + + /** + * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: The port to release the VCPI slots from + * + * Releases any VCPI slots that have been allocated to a port in the atomic + * state. Any atomic drivers which support MST must call this function in + * their &drm_connector_helper_funcs.atomic_check() callback when the + * connector will no longer have VCPI allocated (e.g. because its CRTC was + * removed) when it had VCPI allocated in the previous atomic state. + * + * It is OK to call this even if @port has been removed from the system. + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check + * phase. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * 0 if all slots for this port were added back to + * &drm_dp_mst_topology_state.avail_slots or negative error code + */ + int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) + { + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + if (WARN_ON(!found)) { + drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n", + port, &topology_state->base); + return -EINVAL; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi); + if (pos->vcpi) { + drm_dp_mst_put_port_malloc(port); + pos->vcpi = 0; + pos->pbn = 0; + } + + return 0; + } + EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); + + /** + * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format + * @mst_state: mst_state to update + * @link_encoding_cap: the ecoding format on the link + */ + void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap) + { + if (link_encoding_cap == DP_CAP_ANSI_128B132B) { + mst_state->total_avail_slots = 64; + mst_state->start_slot = 0; + } else { + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + } + + DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n", + (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b", + mst_state); + } + EXPORT_SYMBOL(drm_dp_mst_update_slots); + + /** + * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel + * @mgr: manager for this port + * @port: port to allocate a virtual channel for. + * @pbn: payload bandwidth number to request + * @slots: returned number of slots for this PBN. + */ + bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, int slots) + { + int ret; + + if (slots < 0) + return false; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return false; + + if (port->vcpi.vcpi > 0) { + drm_dbg_kms(mgr->dev, + "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", + port->vcpi.vcpi, port->vcpi.pbn, pbn); + if (pbn == port->vcpi.pbn) { + drm_dp_mst_topology_put_port(port); + return true; + } + } + + ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots); + if (ret) { + drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n", + DIV_ROUND_UP(pbn, mgr->pbn_div), ret); + drm_dp_mst_topology_put_port(port); + goto out; + } + drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots); + + /* Keep port allocated until its payload has been removed */ + drm_dp_mst_get_port_malloc(port); + drm_dp_mst_topology_put_port(port); + return true; + out: + return false; + } + EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi); + + int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) + { + int slots = 0; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return slots; + + slots = port->vcpi.num_slots; + drm_dp_mst_topology_put_port(port); + return slots; + } + EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots); + + /** + * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This just resets the number of slots for the ports VCPI for later programming. + */ + void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) + { + /* + * A port with VCPI will remain allocated until its VCPI is + * released, no verified ref needed + */ + + port->vcpi.num_slots = 0; + } + EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots); + + /** + * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI + * @mgr: manager for this port + * @port: port to deallocate vcpi for + * + * This can be called unconditionally, regardless of whether + * drm_dp_mst_allocate_vcpi() succeeded or not. + */ + void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) + { + bool skip; + + if (!port->vcpi.vcpi) + return; + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + return; + + drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi); + port->vcpi.num_slots = 0; + port->vcpi.pbn = 0; + port->vcpi.aligned_pbn = 0; + port->vcpi.vcpi = 0; + drm_dp_mst_put_port_malloc(port); + } + EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi); + + static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, struct drm_dp_payload *payload) + { + u8 payload_alloc[3], status; + int ret; + int retries = 0; + + drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, + DP_PAYLOAD_TABLE_UPDATED); + + payload_alloc[0] = id; + payload_alloc[1] = payload->start_slot; + payload_alloc[2] = payload->num_slots; + + ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); + if (ret != 3) { + drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret); + goto fail; + } + + retry: + ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret); + goto fail; + } + + if (!(status & DP_PAYLOAD_TABLE_UPDATED)) { + retries++; + if (retries < 20) { + usleep_range(10000, 20000); + goto retry; + } + drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n", + status); + ret = -EINVAL; + goto fail; + } + ret = 0; + fail: + return ret; + } + + static int do_get_act_status(struct drm_dp_aux *aux) + { + int ret; + u8 status; + + ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) + return ret; + + return status; + } + + /** + * drm_dp_check_act_status() - Polls for ACT handled status. + * @mgr: manager to use + * + * Tries waiting for the MST hub to finish updating it's payload table by + * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really + * take that long). + * + * Returns: + * 0 if the ACT was handled in time, negative error code on failure. + */ + int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr) + { + /* + * There doesn't seem to be any recommended retry count or timeout in + * the MST specification. Since some hubs have been observed to take + * over 1 second to update their payload allocations under certain + * conditions, we use a rather large timeout value. + */ + const int timeout_ms = 3000; + int ret, status; + + ret = readx_poll_timeout(do_get_act_status, mgr->aux, status, + status & DP_PAYLOAD_ACT_HANDLED || status < 0, + 200, timeout_ms * USEC_PER_MSEC); + if (ret < 0 && status >= 0) { + drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n", + timeout_ms, status); + return -EINVAL; + } else if (status < 0) { + /* + * Failure here isn't unexpected - the hub may have + * just been unplugged + */ + drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status); + return status; + } + + return 0; + } + EXPORT_SYMBOL(drm_dp_check_act_status); + + /** + * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. + * @clock: dot clock for the mode + * @bpp: bpp for the mode. + * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel + * + * This uses the formula in the spec to calculate the PBN value for a mode. + */ + int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc) + { + /* + * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006 + * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on + * common multiplier to render an integer PBN for all link rate/lane + * counts combinations + * calculate + * peak_kbps *= (1006/1000) + * peak_kbps *= (64/54) + * peak_kbps *= 8 convert to bytes + * + * If the bpp is in units of 1/16, further divide by 16. Put this + * factor in the numerator rather than the denominator to avoid + * integer overflow + */ + + if (dsc) + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006), + 8 * 54 * 1000 * 1000); + + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006), + 8 * 54 * 1000 * 1000); + } + EXPORT_SYMBOL(drm_dp_calc_pbn_mode); + + /* we want to kick the TX after we've ack the up/down IRQs. */ + static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr) + { + queue_work(system_long_wq, &mgr->tx_work); + } + + /* + * Helper function for parsing DP device types into convenient strings + * for use with dp_mst_topology + */ + static const char *pdt_to_string(u8 pdt) + { + switch (pdt) { + case DP_PEER_DEVICE_NONE: + return "NONE"; + case DP_PEER_DEVICE_SOURCE_OR_SST: + return "SOURCE OR SST"; + case DP_PEER_DEVICE_MST_BRANCHING: + return "MST BRANCHING"; + case DP_PEER_DEVICE_SST_SINK: + return "SST SINK"; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + return "DP LEGACY CONV"; + default: + return "ERR"; + } + } + + static void drm_dp_mst_dump_mstb(struct seq_file *m, + struct drm_dp_mst_branch *mstb) + { + struct drm_dp_mst_port *port; + int tabs = mstb->lct; + char prefix[10]; + int i; + + for (i = 0; i < tabs; i++) + prefix[i] = '\t'; + prefix[i] = '\0'; + + seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); + list_for_each_entry(port, &mstb->ports, next) { + seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", + prefix, + port->port_num, + port, + port->input ? "input" : "output", + pdt_to_string(port->pdt), + port->ddps, + port->ldps, + port->num_sdp_streams, + port->num_sdp_stream_sinks, + port->fec_capable ? "true" : "false", + port->connector); + if (port->mstb) + drm_dp_mst_dump_mstb(m, port->mstb); + } + } + + #define DP_PAYLOAD_TABLE_SIZE 64 + + static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf) + { + int i; + + for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { + if (drm_dp_dpcd_read(mgr->aux, + DP_PAYLOAD_TABLE_UPDATE_STATUS + i, + &buf[i], 16) != 16) + return false; + } + return true; + } + + static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, char *name, + int namelen) + { + struct edid *mst_edid; + + mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); + drm_edid_get_monitor_name(mst_edid, name, namelen); + } + + /** + * drm_dp_mst_dump_topology(): dump topology to seq file. + * @m: seq_file to dump output to + * @mgr: manager to dump current topology for. + * + * helper to dump MST topology to a seq file for debugfs. + */ + void drm_dp_mst_dump_topology(struct seq_file *m, + struct drm_dp_mst_topology_mgr *mgr) + { + int i; + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) + drm_dp_mst_dump_mstb(m, mgr->mst_primary); + + /* dump VCPIs */ + mutex_unlock(&mgr->lock); + + mutex_lock(&mgr->payload_lock); + seq_printf(m, "\n*** VCPI Info ***\n"); + seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads); + + seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i]) { + char name[14]; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + fetch_monitor_name(mgr, port, name, sizeof(name)); + seq_printf(m, "%10d%10d%10d%10d%20s\n", + i, + port->port_num, + port->vcpi.vcpi, + port->vcpi.num_slots, + (*name != 0) ? name : "Unknown"); + } else + seq_printf(m, "%6d - Unused\n", i); + } + seq_printf(m, "\n*** Payload Info ***\n"); + seq_printf(m, "| idx | state | start slot | # slots |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + seq_printf(m, "%10d%10d%15d%10d\n", + i, + mgr->payloads[i].payload_state, + mgr->payloads[i].start_slot, + mgr->payloads[i].num_slots); + } + mutex_unlock(&mgr->payload_lock); + + seq_printf(m, "\n*** DPCD Info ***\n"); + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + u8 buf[DP_PAYLOAD_TABLE_SIZE]; + int ret; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); + if (ret) { + seq_printf(m, "dpcd read failed\n"); + goto out; + } + seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); + if (ret) { + seq_printf(m, "faux/mst read failed\n"); + goto out; + } + seq_printf(m, "faux/mst: %*ph\n", 2, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); + if (ret) { + seq_printf(m, "mst ctrl read failed\n"); + goto out; + } + seq_printf(m, "mst ctrl: %*ph\n", 1, buf); + + /* dump the standard OUI branch header */ + ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); + if (ret) { + seq_printf(m, "branch oui read failed\n"); + goto out; + } + seq_printf(m, "branch oui: %*phN devid: ", 3, buf); + + for (i = 0x3; i < 0x8 && buf[i]; i++) + seq_printf(m, "%c", buf[i]); + seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", + buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); + if (dump_dp_payload_table(mgr, buf)) + seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf); + } + + out: + mutex_unlock(&mgr->lock); + + } + EXPORT_SYMBOL(drm_dp_mst_dump_topology); + + static void drm_dp_tx_work(struct work_struct *work) + { + struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work); + + mutex_lock(&mgr->qlock); + if (!list_empty(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); + } + + static inline void + drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port) + { + drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs); + + if (port->connector) { + drm_connector_unregister(port->connector); + drm_connector_put(port->connector); + } + + drm_dp_mst_put_port_malloc(port); + } + + static inline void + drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb) + { + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port, *port_tmp; + struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp; + bool wake_tx = false; + + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) { + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + } + mutex_unlock(&mgr->lock); + + /* drop any tx slot msg */ + mutex_lock(&mstb->mgr->qlock); + list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) { + if (txmsg->dst != mstb) + continue; + + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + list_del(&txmsg->next); + wake_tx = true; + } + mutex_unlock(&mstb->mgr->qlock); + + if (wake_tx) + wake_up_all(&mstb->mgr->tx_waitq); + + drm_dp_mst_put_mstb_malloc(mstb); + } + + static void drm_dp_delayed_destroy_work(struct work_struct *work) + { + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + delayed_destroy_work); + bool send_hotplug = false, go_again; + + /* + * Not a regular list traverse as we have to drop the destroy + * connector lock before destroying the mstb/port, to avoid AB->BA + * ordering between this lock and the config mutex. + */ + do { + go_again = false; + + for (;;) { + struct drm_dp_mst_branch *mstb; + + mutex_lock(&mgr->delayed_destroy_lock); + mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list, + struct drm_dp_mst_branch, + destroy_next); + if (mstb) + list_del(&mstb->destroy_next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!mstb) + break; + + drm_dp_delayed_destroy_mstb(mstb); + go_again = true; + } + + for (;;) { + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->delayed_destroy_lock); + port = list_first_entry_or_null(&mgr->destroy_port_list, + struct drm_dp_mst_port, + next); + if (port) + list_del(&port->next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!port) + break; + + drm_dp_delayed_destroy_port(port); + send_hotplug = true; + go_again = true; + } + } while (go_again); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); + } + + static struct drm_private_state * + drm_dp_mst_duplicate_state(struct drm_private_obj *obj) + { + struct drm_dp_mst_topology_state *state, *old_state = + to_dp_mst_topology_state(obj->state); + struct drm_dp_vcpi_allocation *pos, *vcpi; + + state = kmemdup(old_state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + INIT_LIST_HEAD(&state->vcpis); + + list_for_each_entry(pos, &old_state->vcpis, next) { + /* Prune leftover freed VCPI allocations */ + if (!pos->vcpi) + continue; + + vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + goto fail; + + drm_dp_mst_get_port_malloc(vcpi->port); + list_add(&vcpi->next, &state->vcpis); + } + + return &state->base; + + fail: + list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) { + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + kfree(state); + + return NULL; + } + + static void drm_dp_mst_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) + { + struct drm_dp_mst_topology_state *mst_state = + to_dp_mst_topology_state(state); + struct drm_dp_vcpi_allocation *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) { + /* We only keep references to ports with non-zero VCPIs */ + if (pos->vcpi) + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + + kfree(mst_state); + } + + static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch) + { + while (port->parent) { + if (port->parent == branch) + return true; + + if (port->parent->port_parent) + port = port->parent->port_parent; + else + break; + } + return false; + } + + static int + drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state); + + static int + drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_topology_state *state) + { + struct drm_dp_vcpi_allocation *vcpi; + struct drm_dp_mst_port *port; + int pbn_used = 0, ret; + bool found = false; + + /* Check that we have at least one port in our state that's downstream + * of this branch, otherwise we can skip this branch + */ + list_for_each_entry(vcpi, &state->vcpis, next) { + if (!vcpi->pbn || + !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) + continue; + + found = true; + break; + } + if (!found) + return 0; + + if (mstb->port_parent) + drm_dbg_atomic(mstb->mgr->dev, + "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", + mstb->port_parent->parent, mstb->port_parent, mstb); + else + drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb); + + list_for_each_entry(port, &mstb->ports, next) { + ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); + if (ret < 0) + return ret; + + pbn_used += ret; + } + + return pbn_used; + } + + static int + drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state) + { + struct drm_dp_vcpi_allocation *vcpi; + int pbn_used = 0; + + if (port->pdt == DP_PEER_DEVICE_NONE) + return 0; + + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + bool found = false; + + list_for_each_entry(vcpi, &state->vcpis, next) { + if (vcpi->port != port) + continue; + if (!vcpi->pbn) + return 0; + + found = true; + break; + } + if (!found) + return 0; + + /* + * This could happen if the sink deasserted its HPD line, but + * the branch device still reports it as attached (PDT != NONE). + */ + if (!port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] no BW available for the port\n", + port->parent, port); + return -EINVAL; + } + + pbn_used = vcpi->pbn; + } else { + pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, + state); + if (pbn_used <= 0) + return pbn_used; + } + + if (pbn_used > port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", + port->parent, port, pbn_used, port->full_pbn); + return -ENOSPC; + } + + drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", + port->parent, port, pbn_used, port->full_pbn); + + return pbn_used; + } + + static inline int + drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_state *mst_state) + { + struct drm_dp_vcpi_allocation *vcpi; + int avail_slots = mst_state->total_avail_slots, payload_count = 0; + + list_for_each_entry(vcpi, &mst_state->vcpis, next) { + /* Releasing VCPI is always OK-even if the port is gone */ + if (!vcpi->vcpi) { + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n", + vcpi->port); + continue; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n", + vcpi->port, vcpi->vcpi); + + avail_slots -= vcpi->vcpi; + if (avail_slots < 0) { + drm_dbg_atomic(mgr->dev, + "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n", + vcpi->port, mst_state, avail_slots + vcpi->vcpi); + return -ENOSPC; + } + + if (++payload_count > mgr->max_payloads) { + drm_dbg_atomic(mgr->dev, + "[MST MGR:%p] state %p has too many payloads (max=%d)\n", + mgr, mst_state, mgr->max_payloads); + return -EINVAL; + } + } + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n", + mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots); + + return 0; + } + + /** + * drm_dp_mst_add_affected_dsc_crtcs + * @state: Pointer to the new struct drm_dp_mst_topology_state + * @mgr: MST topology manager + * + * Whenever there is a change in mst topology + * DSC configuration would have to be recalculated + * therefore we need to trigger modeset on all affected + * CRTCs in that topology + * + * See also: + * drm_dp_mst_atomic_enable_dsc() + */ + int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr) + { + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + mst_state = drm_atomic_get_mst_topology_state(state, mgr); + + if (IS_ERR(mst_state)) + return -EINVAL; + + list_for_each_entry(pos, &mst_state->vcpis, next) { + + connector = pos->port->connector; + + if (!connector) + return -EINVAL; + + conn_state = drm_atomic_get_connector_state(state, connector); + + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + crtc = conn_state->crtc; + + if (!crtc) + continue; + + if (!drm_dp_mst_dsc_aux_for_port(pos->port)) + continue; + + crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc); + + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n", + mgr, crtc); + + crtc_state->mode_changed = true; + } + return 0; + } + EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs); + + /** + * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off + * @state: Pointer to the new drm_atomic_state + * @port: Pointer to the affected MST Port + * @pbn: Newly recalculated bw required for link with DSC enabled + * @pbn_div: Divider to calculate correct number of pbn per slot + * @enable: Boolean flag to enable or disable DSC on the port + * + * This function enables DSC on the given Port + * by recalculating its vcpi from pbn provided + * and sets dsc_enable flag to keep track of which + * ports have DSC enabled + * + */ + int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, + struct drm_dp_mst_port *port, + int pbn, int pbn_div, + bool enable) + { + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + int vcpi = 0; + + mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); + + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + list_for_each_entry(pos, &mst_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + + if (!found) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n", + port, mst_state); + return -EINVAL; + } + + if (pos->dsc_enabled == enable) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n", + port, enable, pos->vcpi); + vcpi = pos->vcpi; + } + + if (enable) { + vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div); + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n", + port, vcpi); + if (vcpi < 0) + return -EINVAL; + } + + pos->dsc_enabled = enable; + + return vcpi; + } + EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc); + /** + * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an + * atomic update is valid + * @state: Pointer to the new &struct drm_dp_mst_topology_state + * + * Checks the given topology state for an atomic update to ensure that it's + * valid. This includes checking whether there's enough bandwidth to support + * the new VCPI allocations in the atomic update. + * + * Any atomic drivers supporting DP MST must make sure to call this after + * checking the rest of their state in their + * &drm_mode_config_funcs.atomic_check() callback. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_atomic_release_vcpi_slots() + * + * Returns: + * + * 0 if the new state is valid, negative error code otherwise. + */ + int drm_dp_mst_atomic_check(struct drm_atomic_state *state) + { + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + int i, ret = 0; + + for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { + if (!mgr->mst_state) + continue; + + ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); + if (ret) + break; + + mutex_lock(&mgr->lock); + ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, + mst_state); + mutex_unlock(&mgr->lock); + if (ret < 0) + break; + else + ret = 0; + } + + return ret; + } + EXPORT_SYMBOL(drm_dp_mst_atomic_check); + + const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = { + .atomic_duplicate_state = drm_dp_mst_duplicate_state, + .atomic_destroy_state = drm_dp_mst_destroy_state, + }; + EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs); + + /** + * drm_atomic_get_mst_topology_state: get MST topology state + * + * @state: global atomic state + * @mgr: MST topology manager, also the private object in this case + * + * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic + * state vtable so that the private object state returned is that of a MST + * topology object. Also, drm_atomic_get_private_obj_state() expects the caller + * to care of the locking, so warn if don't hold the connection_mutex. + * + * RETURNS: + * + * The MST topology state or error pointer. + */ + struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr) + { + return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base)); + } + EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); + + /** + * drm_dp_mst_topology_mgr_init - initialise a topology manager + * @mgr: manager struct to initialise + * @dev: device providing this structure - for i2c addition. + * @aux: DP helper aux channel to talk to this device + * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit + * @max_payloads: maximum number of payloads this GPU can source + * @max_lane_count: maximum number of lanes this GPU supports + * @max_link_rate: maximum link rate per lane this GPU supports in kHz + * @conn_base_id: the connector object ID the MST device is connected to. + * + * Return 0 for success, or negative error code on failure + */ + int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, + struct drm_device *dev, struct drm_dp_aux *aux, + int max_dpcd_transaction_bytes, int max_payloads, + int max_lane_count, int max_link_rate, + int conn_base_id) + { + struct drm_dp_mst_topology_state *mst_state; + + mutex_init(&mgr->lock); + mutex_init(&mgr->qlock); + mutex_init(&mgr->payload_lock); + mutex_init(&mgr->delayed_destroy_lock); + mutex_init(&mgr->up_req_lock); + mutex_init(&mgr->probe_lock); + #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_init(&mgr->topology_ref_history_lock); ++ stack_depot_init(); + #endif + INIT_LIST_HEAD(&mgr->tx_msg_downq); + INIT_LIST_HEAD(&mgr->destroy_port_list); + INIT_LIST_HEAD(&mgr->destroy_branch_device_list); + INIT_LIST_HEAD(&mgr->up_req_list); + + /* + * delayed_destroy_work will be queued on a dedicated WQ, so that any + * requeuing will be also flushed when deiniting the topology manager. + */ + mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); + if (mgr->delayed_destroy_wq == NULL) + return -ENOMEM; + + INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work); + INIT_WORK(&mgr->tx_work, drm_dp_tx_work); + INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work); + INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work); + init_waitqueue_head(&mgr->tx_waitq); + mgr->dev = dev; + mgr->aux = aux; + mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes; + mgr->max_payloads = max_payloads; + mgr->max_lane_count = max_lane_count; + mgr->max_link_rate = max_link_rate; + mgr->conn_base_id = conn_base_id; + if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 || + max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8) + return -EINVAL; + mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL); + if (!mgr->payloads) + return -ENOMEM; + mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL); + if (!mgr->proposed_vcpis) + return -ENOMEM; + set_bit(0, &mgr->payload_mask); + + mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL); + if (mst_state == NULL) + return -ENOMEM; + + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + + mst_state->mgr = mgr; + INIT_LIST_HEAD(&mst_state->vcpis); + + drm_atomic_private_obj_init(dev, &mgr->base, + &mst_state->base, + &drm_dp_mst_topology_state_funcs); + + return 0; + } + EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); + + /** + * drm_dp_mst_topology_mgr_destroy() - destroy topology manager. + * @mgr: manager to destroy + */ + void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) + { + drm_dp_mst_topology_mgr_set_mst(mgr, false); + flush_work(&mgr->work); + /* The following will also drain any requeued work on the WQ. */ + if (mgr->delayed_destroy_wq) { + destroy_workqueue(mgr->delayed_destroy_wq); + mgr->delayed_destroy_wq = NULL; + } + mutex_lock(&mgr->payload_lock); + kfree(mgr->payloads); + mgr->payloads = NULL; + kfree(mgr->proposed_vcpis); + mgr->proposed_vcpis = NULL; + mutex_unlock(&mgr->payload_lock); + mgr->dev = NULL; + mgr->aux = NULL; + drm_atomic_private_obj_fini(&mgr->base); + mgr->funcs = NULL; + + mutex_destroy(&mgr->delayed_destroy_lock); + mutex_destroy(&mgr->payload_lock); + mutex_destroy(&mgr->qlock); + mutex_destroy(&mgr->lock); + mutex_destroy(&mgr->up_req_lock); + mutex_destroy(&mgr->probe_lock); + #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_destroy(&mgr->topology_ref_history_lock); + #endif + } + EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); + + static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num) + { + int i; + + if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS) + return false; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || + msgs[i].len > 0xff) + return false; + } + + return msgs[num - 1].flags & I2C_M_RD && + msgs[num - 1].len <= 0xff; + } + + static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num) + { + int i; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) || + msgs[i].len > 0xff) + return false; + } + + return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff; + } + + static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) + { + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_READ; + msg.u.i2c_read.num_transactions = num - 1; + msg.u.i2c_read.port_number = port->port_num; + for (i = 0; i < num - 1; i++) { + msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr; + msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len; + msg.u.i2c_read.transactions[i].bytes = msgs[i].buf; + msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP); + } + msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr; + msg.u.i2c_read.num_bytes_read = msgs[num - 1].len; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + + txmsg->dst = mstb; + drm_dp_encode_sideband_req(&msg, txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) { + ret = -EIO; + goto out; + } + memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len); + ret = num; + } + out: + kfree(txmsg); + return ret; + } + + static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) + { + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < num; i++) { + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_WRITE; + msg.u.i2c_write.port_number = port->port_num; + msg.u.i2c_write.write_i2c_device_id = msgs[i].addr; + msg.u.i2c_write.num_bytes = msgs[i].len; + msg.u.i2c_write.bytes = msgs[i].buf; + + memset(txmsg, 0, sizeof(*txmsg)); + txmsg->dst = mstb; + + drm_dp_encode_sideband_req(&msg, txmsg); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + } else { + goto out; + } + } + ret = num; + out: + kfree(txmsg); + return ret; + } + + /* I2C device */ + static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) + { + struct drm_dp_aux *aux = adapter->algo_data; + struct drm_dp_mst_port *port = + container_of(aux, struct drm_dp_mst_port, aux); + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + int ret; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EREMOTEIO; + + if (remote_i2c_read_ok(msgs, num)) { + ret = drm_dp_mst_i2c_read(mstb, port, msgs, num); + } else if (remote_i2c_write_ok(msgs, num)) { + ret = drm_dp_mst_i2c_write(mstb, port, msgs, num); + } else { + drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n"); + ret = -EIO; + } + + drm_dp_mst_topology_put_mstb(mstb); + return ret; + } + + static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter) + { + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; + } + + static const struct i2c_algorithm drm_dp_mst_i2c_algo = { + .functionality = drm_dp_mst_i2c_functionality, + .master_xfer = drm_dp_mst_i2c_xfer, + }; + + /** + * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX + * @port: The port to add the I2C bus on + * + * Returns 0 on success or a negative error code on failure. + */ + static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port) + { + struct drm_dp_aux *aux = &port->aux; + struct device *parent_dev = port->mgr->dev->dev; + + aux->ddc.algo = &drm_dp_mst_i2c_algo; + aux->ddc.algo_data = aux; + aux->ddc.retries = 3; + + aux->ddc.class = I2C_CLASS_DDC; + aux->ddc.owner = THIS_MODULE; + /* FIXME: set the kdev of the port's connector as parent */ + aux->ddc.dev.parent = parent_dev; + aux->ddc.dev.of_node = parent_dev->of_node; + + strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev), + sizeof(aux->ddc.name)); + + return i2c_add_adapter(&aux->ddc); + } + + /** + * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter + * @port: The port to remove the I2C bus from + */ + static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port) + { + i2c_del_adapter(&port->aux.ddc); + } + + /** + * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device + * @port: The port to check + * + * A single physical MST hub object can be represented in the topology + * by multiple branches, with virtual ports between those branches. + * + * As of DP1.4, An MST hub with internal (virtual) ports must expose + * certain DPCD registers over those ports. See sections 2.6.1.1.1 + * and 2.6.1.1.2 of Display Port specification v1.4 for details. + * + * May acquire mgr->lock + * + * Returns: + * true if the port is a virtual DP peer device, false otherwise + */ + static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port) + { + struct drm_dp_mst_port *downstream_port; + + if (!port || port->dpcd_rev < DP_DPCD_REV_14) + return false; + + /* Virtual DP Sink (Internal Display Panel) */ + if (port->port_num >= 8) + return true; + + /* DP-to-HDMI Protocol Converter */ + if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && + !port->mcs && + port->ldps) + return true; + + /* DP-to-DP */ + mutex_lock(&port->mgr->lock); + if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mstb && + port->mstb->num_ports == 2) { + list_for_each_entry(downstream_port, &port->mstb->ports, next) { + if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK && + !downstream_port->input) { + mutex_unlock(&port->mgr->lock); + return true; + } + } + } + mutex_unlock(&port->mgr->lock); + + return false; + } + + /** + * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC + * @port: The port to check. A leaf of the MST tree with an attached display. + * + * Depending on the situation, DSC may be enabled via the endpoint aux, + * the immediately upstream aux, or the connector's physical aux. + * + * This is both the correct aux to read DSC_CAPABILITY and the + * correct aux to write DSC_ENABLED. + * + * This operation can be expensive (up to four aux reads), so + * the caller should cache the return. + * + * Returns: + * NULL if DSC cannot be enabled on this port, otherwise the aux device + */ + struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port) + { + struct drm_dp_mst_port *immediate_upstream_port; + struct drm_dp_mst_port *fec_port; + struct drm_dp_desc desc = {}; + u8 endpoint_fec; + u8 endpoint_dsc; + + if (!port) + return NULL; + + if (port->parent->port_parent) + immediate_upstream_port = port->parent->port_parent; + else + immediate_upstream_port = NULL; + + fec_port = immediate_upstream_port; + while (fec_port) { + /* + * Each physical link (i.e. not a virtual port) between the + * output and the primary device must support FEC + */ + if (!drm_dp_mst_is_virtual_dpcd(fec_port) && + !fec_port->fec_capable) + return NULL; + + fec_port = fec_port->parent->port_parent; + } + + /* DP-to-DP peer device */ + if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { + u8 upstream_dsc; + + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&immediate_upstream_port->aux, + DP_DSC_SUPPORT, &upstream_dsc, 1) != 1) + return NULL; + + /* Enpoint decompression with DP-to-DP peer device */ + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE) && + (upstream_dsc & 0x2) /* DSC passthrough */) + return &port->aux; + + /* Virtual DPCD decompression with DP-to-DP peer device */ + return &immediate_upstream_port->aux; + } + + /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ + if (drm_dp_mst_is_virtual_dpcd(port)) + return &port->aux; + + /* + * Synaptics quirk + * Applies to ports for which: + * - Physical aux has Synaptics OUI + * - DPv1.4 or higher + * - Port is on primary branch device + * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG) + */ + if (drm_dp_read_desc(port->mgr->aux, &desc, true)) + return NULL; + + if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) && + port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 && + port->parent == port->mgr->mst_primary) { + u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; + + if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0) + return NULL; + + if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) && + ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) + != DP_DWN_STRM_PORT_TYPE_ANALOG)) + return port->mgr->aux; + } + + /* + * The check below verifies if the MST sink + * connected to the GPU is capable of DSC - + * therefore the endpoint needs to be + * both DSC and FEC capable. + */ + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE)) + return &port->aux; + + return NULL; + } + EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port); diff --cc drivers/gpu/drm/exynos/Kconfig index 6a251e3aa779,6a251e3aa779..f27cfd2a9726 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@@ -66,6 -66,6 +66,7 @@@ config DRM_EXYNOS_D bool "Exynos specific extensions for Analogix DP driver" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS7_DECON select DRM_ANALOGIX_DP ++ select DRM_DP_HELPER default DRM_EXYNOS select DRM_PANEL help diff --cc drivers/gpu/drm/i915/Makefile index 1b62b9f65196,72c2e9c5e0b3..b136234120c4 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@@ -161,9 -161,7 +161,8 @@@ gem-y += i915-y += \ $(gem-y) \ i915_active.o \ - i915_buddy.o \ i915_cmd_parser.o \ + i915_deps.o \ i915_gem_evict.o \ i915_gem_gtt.o \ i915_gem_ww.o \ diff --cc drivers/gpu/drm/i915/display/intel_display_types.h index c9c6efadf8b4,0d68dec6ac83..41e3dd25a78f --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@@ -32,11 -32,10 +32,11 @@@ #include #include + #include + #include #include #include - #include - #include +#include #include #include #include diff --cc drivers/gpu/drm/rockchip/Kconfig index 9f1ecefc3933,d59dca5efb52..fa5cfda4e90e --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@@ -7,6 -8,6 +8,7 @@@ config DRM_ROCKCHI select DRM_PANEL select VIDEOMODE_HELPERS select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP ++ select DRM_DP_HELPER if ROCKCHIP_ANALOGIX_DP select DRM_DW_HDMI if ROCKCHIP_DW_HDMI select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI select GENERIC_PHY if ROCKCHIP_DW_MIPI_DSI diff --cc include/drm/dp/drm_dp_helper.h index 000000000000,16d6da3a129f..98d020835b49 mode 000000,100644..100644 --- a/include/drm/dp/drm_dp_helper.h +++ b/include/drm/dp/drm_dp_helper.h @@@ -1,0 -1,2358 +1,2365 @@@ + /* + * Copyright © 2008 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + + #ifndef _DRM_DP_HELPER_H_ + #define _DRM_DP_HELPER_H_ + + #include + #include + #include + #include + + struct drm_device; + struct drm_dp_aux; + struct drm_panel; + + /* + * Unless otherwise noted, all values are from the DP 1.1a spec. Note that + * DP and DPCD versions are independent. Differences from 1.0 are not noted, + * 1.0 devices basically don't exist in the wild. + * + * Abbreviations, in chronological order: + * + * eDP: Embedded DisplayPort version 1 + * DPI: DisplayPort Interoperability Guideline v1.1a + * 1.2: DisplayPort 1.2 + * MST: Multistream Transport - part of DP 1.2a + * + * 1.2 formally includes both eDP and DPI definitions. + */ + + /* MSA (Main Stream Attribute) MISC bits (as MISC1<<8|MISC0) */ + #define DP_MSA_MISC_SYNC_CLOCK (1 << 0) + #define DP_MSA_MISC_INTERLACE_VTOTAL_EVEN (1 << 8) + #define DP_MSA_MISC_STEREO_NO_3D (0 << 9) + #define DP_MSA_MISC_STEREO_PROG_RIGHT_EYE (1 << 9) + #define DP_MSA_MISC_STEREO_PROG_LEFT_EYE (3 << 9) + /* bits per component for non-RAW */ + #define DP_MSA_MISC_6_BPC (0 << 5) + #define DP_MSA_MISC_8_BPC (1 << 5) + #define DP_MSA_MISC_10_BPC (2 << 5) + #define DP_MSA_MISC_12_BPC (3 << 5) + #define DP_MSA_MISC_16_BPC (4 << 5) + /* bits per component for RAW */ + #define DP_MSA_MISC_RAW_6_BPC (1 << 5) + #define DP_MSA_MISC_RAW_7_BPC (2 << 5) + #define DP_MSA_MISC_RAW_8_BPC (3 << 5) + #define DP_MSA_MISC_RAW_10_BPC (4 << 5) + #define DP_MSA_MISC_RAW_12_BPC (5 << 5) + #define DP_MSA_MISC_RAW_14_BPC (6 << 5) + #define DP_MSA_MISC_RAW_16_BPC (7 << 5) + /* pixel encoding/colorimetry format */ + #define _DP_MSA_MISC_COLOR(misc1_7, misc0_21, misc0_3, misc0_4) \ + ((misc1_7) << 15 | (misc0_4) << 4 | (misc0_3) << 3 | ((misc0_21) << 1)) + #define DP_MSA_MISC_COLOR_RGB _DP_MSA_MISC_COLOR(0, 0, 0, 0) + #define DP_MSA_MISC_COLOR_CEA_RGB _DP_MSA_MISC_COLOR(0, 0, 1, 0) + #define DP_MSA_MISC_COLOR_RGB_WIDE_FIXED _DP_MSA_MISC_COLOR(0, 3, 0, 0) + #define DP_MSA_MISC_COLOR_RGB_WIDE_FLOAT _DP_MSA_MISC_COLOR(0, 3, 0, 1) + #define DP_MSA_MISC_COLOR_Y_ONLY _DP_MSA_MISC_COLOR(1, 0, 0, 0) + #define DP_MSA_MISC_COLOR_RAW _DP_MSA_MISC_COLOR(1, 1, 0, 0) + #define DP_MSA_MISC_COLOR_YCBCR_422_BT601 _DP_MSA_MISC_COLOR(0, 1, 1, 0) + #define DP_MSA_MISC_COLOR_YCBCR_422_BT709 _DP_MSA_MISC_COLOR(0, 1, 1, 1) + #define DP_MSA_MISC_COLOR_YCBCR_444_BT601 _DP_MSA_MISC_COLOR(0, 2, 1, 0) + #define DP_MSA_MISC_COLOR_YCBCR_444_BT709 _DP_MSA_MISC_COLOR(0, 2, 1, 1) + #define DP_MSA_MISC_COLOR_XVYCC_422_BT601 _DP_MSA_MISC_COLOR(0, 1, 0, 0) + #define DP_MSA_MISC_COLOR_XVYCC_422_BT709 _DP_MSA_MISC_COLOR(0, 1, 0, 1) + #define DP_MSA_MISC_COLOR_XVYCC_444_BT601 _DP_MSA_MISC_COLOR(0, 2, 0, 0) + #define DP_MSA_MISC_COLOR_XVYCC_444_BT709 _DP_MSA_MISC_COLOR(0, 2, 0, 1) + #define DP_MSA_MISC_COLOR_OPRGB _DP_MSA_MISC_COLOR(0, 0, 1, 1) + #define DP_MSA_MISC_COLOR_DCI_P3 _DP_MSA_MISC_COLOR(0, 3, 1, 0) + #define DP_MSA_MISC_COLOR_COLOR_PROFILE _DP_MSA_MISC_COLOR(0, 3, 1, 1) + #define DP_MSA_MISC_COLOR_VSC_SDP (1 << 14) + + #define DP_AUX_MAX_PAYLOAD_BYTES 16 + + #define DP_AUX_I2C_WRITE 0x0 + #define DP_AUX_I2C_READ 0x1 + #define DP_AUX_I2C_WRITE_STATUS_UPDATE 0x2 + #define DP_AUX_I2C_MOT 0x4 + #define DP_AUX_NATIVE_WRITE 0x8 + #define DP_AUX_NATIVE_READ 0x9 + + #define DP_AUX_NATIVE_REPLY_ACK (0x0 << 0) + #define DP_AUX_NATIVE_REPLY_NACK (0x1 << 0) + #define DP_AUX_NATIVE_REPLY_DEFER (0x2 << 0) + #define DP_AUX_NATIVE_REPLY_MASK (0x3 << 0) + + #define DP_AUX_I2C_REPLY_ACK (0x0 << 2) + #define DP_AUX_I2C_REPLY_NACK (0x1 << 2) + #define DP_AUX_I2C_REPLY_DEFER (0x2 << 2) + #define DP_AUX_I2C_REPLY_MASK (0x3 << 2) + + /* DPCD Field Address Mapping */ + + /* Receiver Capability */ + #define DP_DPCD_REV 0x000 + # define DP_DPCD_REV_10 0x10 + # define DP_DPCD_REV_11 0x11 + # define DP_DPCD_REV_12 0x12 + # define DP_DPCD_REV_13 0x13 + # define DP_DPCD_REV_14 0x14 + + #define DP_MAX_LINK_RATE 0x001 + + #define DP_MAX_LANE_COUNT 0x002 + # define DP_MAX_LANE_COUNT_MASK 0x1f + # define DP_TPS3_SUPPORTED (1 << 6) /* 1.2 */ + # define DP_ENHANCED_FRAME_CAP (1 << 7) + + #define DP_MAX_DOWNSPREAD 0x003 + # define DP_MAX_DOWNSPREAD_0_5 (1 << 0) + # define DP_STREAM_REGENERATION_STATUS_CAP (1 << 1) /* 2.0 */ + # define DP_NO_AUX_HANDSHAKE_LINK_TRAINING (1 << 6) + # define DP_TPS4_SUPPORTED (1 << 7) + + #define DP_NORP 0x004 + + #define DP_DOWNSTREAMPORT_PRESENT 0x005 + # define DP_DWN_STRM_PORT_PRESENT (1 << 0) + # define DP_DWN_STRM_PORT_TYPE_MASK 0x06 + # define DP_DWN_STRM_PORT_TYPE_DP (0 << 1) + # define DP_DWN_STRM_PORT_TYPE_ANALOG (1 << 1) + # define DP_DWN_STRM_PORT_TYPE_TMDS (2 << 1) + # define DP_DWN_STRM_PORT_TYPE_OTHER (3 << 1) + # define DP_FORMAT_CONVERSION (1 << 3) + # define DP_DETAILED_CAP_INFO_AVAILABLE (1 << 4) /* DPI */ + + #define DP_MAIN_LINK_CHANNEL_CODING 0x006 + # define DP_CAP_ANSI_8B10B (1 << 0) + # define DP_CAP_ANSI_128B132B (1 << 1) /* 2.0 */ + + #define DP_DOWN_STREAM_PORT_COUNT 0x007 + # define DP_PORT_COUNT_MASK 0x0f + # define DP_MSA_TIMING_PAR_IGNORED (1 << 6) /* eDP */ + # define DP_OUI_SUPPORT (1 << 7) + + #define DP_RECEIVE_PORT_0_CAP_0 0x008 + # define DP_LOCAL_EDID_PRESENT (1 << 1) + # define DP_ASSOCIATED_TO_PRECEDING_PORT (1 << 2) + + #define DP_RECEIVE_PORT_0_BUFFER_SIZE 0x009 + + #define DP_RECEIVE_PORT_1_CAP_0 0x00a + #define DP_RECEIVE_PORT_1_BUFFER_SIZE 0x00b + + #define DP_I2C_SPEED_CAP 0x00c /* DPI */ + # define DP_I2C_SPEED_1K 0x01 + # define DP_I2C_SPEED_5K 0x02 + # define DP_I2C_SPEED_10K 0x04 + # define DP_I2C_SPEED_100K 0x08 + # define DP_I2C_SPEED_400K 0x10 + # define DP_I2C_SPEED_1M 0x20 + + #define DP_EDP_CONFIGURATION_CAP 0x00d /* XXX 1.2? */ + # define DP_ALTERNATE_SCRAMBLER_RESET_CAP (1 << 0) + # define DP_FRAMING_CHANGE_CAP (1 << 1) + # define DP_DPCD_DISPLAY_CONTROL_CAPABLE (1 << 3) /* edp v1.2 or higher */ + + #define DP_TRAINING_AUX_RD_INTERVAL 0x00e /* XXX 1.2? */ + # define DP_TRAINING_AUX_RD_MASK 0x7F /* DP 1.3 */ + # define DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT (1 << 7) /* DP 1.3 */ + + #define DP_ADAPTER_CAP 0x00f /* 1.2 */ + # define DP_FORCE_LOAD_SENSE_CAP (1 << 0) + # define DP_ALTERNATE_I2C_PATTERN_CAP (1 << 1) + + #define DP_SUPPORTED_LINK_RATES 0x010 /* eDP 1.4 */ + # define DP_MAX_SUPPORTED_RATES 8 /* 16-bit little-endian */ + + /* Multiple stream transport */ + #define DP_FAUX_CAP 0x020 /* 1.2 */ + # define DP_FAUX_CAP_1 (1 << 0) + + #define DP_SINK_VIDEO_FALLBACK_FORMATS 0x020 /* 2.0 */ + # define DP_FALLBACK_1024x768_60HZ_24BPP (1 << 0) + # define DP_FALLBACK_1280x720_60HZ_24BPP (1 << 1) + # define DP_FALLBACK_1920x1080_60HZ_24BPP (1 << 2) + + #define DP_MSTM_CAP 0x021 /* 1.2 */ + # define DP_MST_CAP (1 << 0) + # define DP_SINGLE_STREAM_SIDEBAND_MSG (1 << 1) /* 2.0 */ + + #define DP_NUMBER_OF_AUDIO_ENDPOINTS 0x022 /* 1.2 */ + + /* AV_SYNC_DATA_BLOCK 1.2 */ + #define DP_AV_GRANULARITY 0x023 + # define DP_AG_FACTOR_MASK (0xf << 0) + # define DP_AG_FACTOR_3MS (0 << 0) + # define DP_AG_FACTOR_2MS (1 << 0) + # define DP_AG_FACTOR_1MS (2 << 0) + # define DP_AG_FACTOR_500US (3 << 0) + # define DP_AG_FACTOR_200US (4 << 0) + # define DP_AG_FACTOR_100US (5 << 0) + # define DP_AG_FACTOR_10US (6 << 0) + # define DP_AG_FACTOR_1US (7 << 0) + # define DP_VG_FACTOR_MASK (0xf << 4) + # define DP_VG_FACTOR_3MS (0 << 4) + # define DP_VG_FACTOR_2MS (1 << 4) + # define DP_VG_FACTOR_1MS (2 << 4) + # define DP_VG_FACTOR_500US (3 << 4) + # define DP_VG_FACTOR_200US (4 << 4) + # define DP_VG_FACTOR_100US (5 << 4) + + #define DP_AUD_DEC_LAT0 0x024 + #define DP_AUD_DEC_LAT1 0x025 + + #define DP_AUD_PP_LAT0 0x026 + #define DP_AUD_PP_LAT1 0x027 + + #define DP_VID_INTER_LAT 0x028 + + #define DP_VID_PROG_LAT 0x029 + + #define DP_REP_LAT 0x02a + + #define DP_AUD_DEL_INS0 0x02b + #define DP_AUD_DEL_INS1 0x02c + #define DP_AUD_DEL_INS2 0x02d + /* End of AV_SYNC_DATA_BLOCK */ + + #define DP_RECEIVER_ALPM_CAP 0x02e /* eDP 1.4 */ + # define DP_ALPM_CAP (1 << 0) + + #define DP_SINK_DEVICE_AUX_FRAME_SYNC_CAP 0x02f /* eDP 1.4 */ + # define DP_AUX_FRAME_SYNC_CAP (1 << 0) + + #define DP_GUID 0x030 /* 1.2 */ + + #define DP_DSC_SUPPORT 0x060 /* DP 1.4 */ + # define DP_DSC_DECOMPRESSION_IS_SUPPORTED (1 << 0) + + #define DP_DSC_REV 0x061 + # define DP_DSC_MAJOR_MASK (0xf << 0) + # define DP_DSC_MINOR_MASK (0xf << 4) + # define DP_DSC_MAJOR_SHIFT 0 + # define DP_DSC_MINOR_SHIFT 4 + + #define DP_DSC_RC_BUF_BLK_SIZE 0x062 + # define DP_DSC_RC_BUF_BLK_SIZE_1 0x0 + # define DP_DSC_RC_BUF_BLK_SIZE_4 0x1 + # define DP_DSC_RC_BUF_BLK_SIZE_16 0x2 + # define DP_DSC_RC_BUF_BLK_SIZE_64 0x3 + + #define DP_DSC_RC_BUF_SIZE 0x063 + + #define DP_DSC_SLICE_CAP_1 0x064 + # define DP_DSC_1_PER_DP_DSC_SINK (1 << 0) + # define DP_DSC_2_PER_DP_DSC_SINK (1 << 1) + # define DP_DSC_4_PER_DP_DSC_SINK (1 << 3) + # define DP_DSC_6_PER_DP_DSC_SINK (1 << 4) + # define DP_DSC_8_PER_DP_DSC_SINK (1 << 5) + # define DP_DSC_10_PER_DP_DSC_SINK (1 << 6) + # define DP_DSC_12_PER_DP_DSC_SINK (1 << 7) + + #define DP_DSC_LINE_BUF_BIT_DEPTH 0x065 + # define DP_DSC_LINE_BUF_BIT_DEPTH_MASK (0xf << 0) + # define DP_DSC_LINE_BUF_BIT_DEPTH_9 0x0 + # define DP_DSC_LINE_BUF_BIT_DEPTH_10 0x1 + # define DP_DSC_LINE_BUF_BIT_DEPTH_11 0x2 + # define DP_DSC_LINE_BUF_BIT_DEPTH_12 0x3 + # define DP_DSC_LINE_BUF_BIT_DEPTH_13 0x4 + # define DP_DSC_LINE_BUF_BIT_DEPTH_14 0x5 + # define DP_DSC_LINE_BUF_BIT_DEPTH_15 0x6 + # define DP_DSC_LINE_BUF_BIT_DEPTH_16 0x7 + # define DP_DSC_LINE_BUF_BIT_DEPTH_8 0x8 + + #define DP_DSC_BLK_PREDICTION_SUPPORT 0x066 + # define DP_DSC_BLK_PREDICTION_IS_SUPPORTED (1 << 0) + + #define DP_DSC_MAX_BITS_PER_PIXEL_LOW 0x067 /* eDP 1.4 */ + + #define DP_DSC_MAX_BITS_PER_PIXEL_HI 0x068 /* eDP 1.4 */ + # define DP_DSC_MAX_BITS_PER_PIXEL_HI_MASK (0x3 << 0) + # define DP_DSC_MAX_BITS_PER_PIXEL_HI_SHIFT 8 + + #define DP_DSC_DEC_COLOR_FORMAT_CAP 0x069 + # define DP_DSC_RGB (1 << 0) + # define DP_DSC_YCbCr444 (1 << 1) + # define DP_DSC_YCbCr422_Simple (1 << 2) + # define DP_DSC_YCbCr422_Native (1 << 3) + # define DP_DSC_YCbCr420_Native (1 << 4) + + #define DP_DSC_DEC_COLOR_DEPTH_CAP 0x06A + # define DP_DSC_8_BPC (1 << 1) + # define DP_DSC_10_BPC (1 << 2) + # define DP_DSC_12_BPC (1 << 3) + + #define DP_DSC_PEAK_THROUGHPUT 0x06B + # define DP_DSC_THROUGHPUT_MODE_0_MASK (0xf << 0) + # define DP_DSC_THROUGHPUT_MODE_0_SHIFT 0 + # define DP_DSC_THROUGHPUT_MODE_0_UNSUPPORTED 0 + # define DP_DSC_THROUGHPUT_MODE_0_340 (1 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_400 (2 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_450 (3 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_500 (4 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_550 (5 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_600 (6 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_650 (7 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_700 (8 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_750 (9 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_800 (10 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_850 (11 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_900 (12 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_950 (13 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_1000 (14 << 0) + # define DP_DSC_THROUGHPUT_MODE_0_170 (15 << 0) /* 1.4a */ + # define DP_DSC_THROUGHPUT_MODE_1_MASK (0xf << 4) + # define DP_DSC_THROUGHPUT_MODE_1_SHIFT 4 + # define DP_DSC_THROUGHPUT_MODE_1_UNSUPPORTED 0 + # define DP_DSC_THROUGHPUT_MODE_1_340 (1 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_400 (2 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_450 (3 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_500 (4 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_550 (5 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_600 (6 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_650 (7 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_700 (8 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_750 (9 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_800 (10 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_850 (11 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_900 (12 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_950 (13 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_1000 (14 << 4) + # define DP_DSC_THROUGHPUT_MODE_1_170 (15 << 4) + + #define DP_DSC_MAX_SLICE_WIDTH 0x06C + #define DP_DSC_MIN_SLICE_WIDTH_VALUE 2560 + #define DP_DSC_SLICE_WIDTH_MULTIPLIER 320 + + #define DP_DSC_SLICE_CAP_2 0x06D + # define DP_DSC_16_PER_DP_DSC_SINK (1 << 0) + # define DP_DSC_20_PER_DP_DSC_SINK (1 << 1) + # define DP_DSC_24_PER_DP_DSC_SINK (1 << 2) + + #define DP_DSC_BITS_PER_PIXEL_INC 0x06F + # define DP_DSC_BITS_PER_PIXEL_1_16 0x0 + # define DP_DSC_BITS_PER_PIXEL_1_8 0x1 + # define DP_DSC_BITS_PER_PIXEL_1_4 0x2 + # define DP_DSC_BITS_PER_PIXEL_1_2 0x3 + # define DP_DSC_BITS_PER_PIXEL_1 0x4 + + #define DP_PSR_SUPPORT 0x070 /* XXX 1.2? */ + # define DP_PSR_IS_SUPPORTED 1 + # define DP_PSR2_IS_SUPPORTED 2 /* eDP 1.4 */ + # define DP_PSR2_WITH_Y_COORD_IS_SUPPORTED 3 /* eDP 1.4a */ + + #define DP_PSR_CAPS 0x071 /* XXX 1.2? */ + # define DP_PSR_NO_TRAIN_ON_EXIT 1 + # define DP_PSR_SETUP_TIME_330 (0 << 1) + # define DP_PSR_SETUP_TIME_275 (1 << 1) + # define DP_PSR_SETUP_TIME_220 (2 << 1) + # define DP_PSR_SETUP_TIME_165 (3 << 1) + # define DP_PSR_SETUP_TIME_110 (4 << 1) + # define DP_PSR_SETUP_TIME_55 (5 << 1) + # define DP_PSR_SETUP_TIME_0 (6 << 1) + # define DP_PSR_SETUP_TIME_MASK (7 << 1) + # define DP_PSR_SETUP_TIME_SHIFT 1 + # define DP_PSR2_SU_Y_COORDINATE_REQUIRED (1 << 4) /* eDP 1.4a */ + # define DP_PSR2_SU_GRANULARITY_REQUIRED (1 << 5) /* eDP 1.4b */ + + #define DP_PSR2_SU_X_GRANULARITY 0x072 /* eDP 1.4b */ + #define DP_PSR2_SU_Y_GRANULARITY 0x074 /* eDP 1.4b */ + + /* + * 0x80-0x8f describe downstream port capabilities, but there are two layouts + * based on whether DP_DETAILED_CAP_INFO_AVAILABLE was set. If it was not, + * each port's descriptor is one byte wide. If it was set, each port's is + * four bytes wide, starting with the one byte from the base info. As of + * DP interop v1.1a only VGA defines additional detail. + */ + + /* offset 0 */ + #define DP_DOWNSTREAM_PORT_0 0x80 + # define DP_DS_PORT_TYPE_MASK (7 << 0) + # define DP_DS_PORT_TYPE_DP 0 + # define DP_DS_PORT_TYPE_VGA 1 + # define DP_DS_PORT_TYPE_DVI 2 + # define DP_DS_PORT_TYPE_HDMI 3 + # define DP_DS_PORT_TYPE_NON_EDID 4 + # define DP_DS_PORT_TYPE_DP_DUALMODE 5 + # define DP_DS_PORT_TYPE_WIRELESS 6 + # define DP_DS_PORT_HPD (1 << 3) + # define DP_DS_NON_EDID_MASK (0xf << 4) + # define DP_DS_NON_EDID_720x480i_60 (1 << 4) + # define DP_DS_NON_EDID_720x480i_50 (2 << 4) + # define DP_DS_NON_EDID_1920x1080i_60 (3 << 4) + # define DP_DS_NON_EDID_1920x1080i_50 (4 << 4) + # define DP_DS_NON_EDID_1280x720_60 (5 << 4) + # define DP_DS_NON_EDID_1280x720_50 (7 << 4) + /* offset 1 for VGA is maximum megapixels per second / 8 */ + /* offset 1 for DVI/HDMI is maximum TMDS clock in Mbps / 2.5 */ + /* offset 2 for VGA/DVI/HDMI */ + # define DP_DS_MAX_BPC_MASK (3 << 0) + # define DP_DS_8BPC 0 + # define DP_DS_10BPC 1 + # define DP_DS_12BPC 2 + # define DP_DS_16BPC 3 + /* HDMI2.1 PCON FRL CONFIGURATION */ + # define DP_PCON_MAX_FRL_BW (7 << 2) + # define DP_PCON_MAX_0GBPS (0 << 2) + # define DP_PCON_MAX_9GBPS (1 << 2) + # define DP_PCON_MAX_18GBPS (2 << 2) + # define DP_PCON_MAX_24GBPS (3 << 2) + # define DP_PCON_MAX_32GBPS (4 << 2) + # define DP_PCON_MAX_40GBPS (5 << 2) + # define DP_PCON_MAX_48GBPS (6 << 2) + # define DP_PCON_SOURCE_CTL_MODE (1 << 5) + + /* offset 3 for DVI */ + # define DP_DS_DVI_DUAL_LINK (1 << 1) + # define DP_DS_DVI_HIGH_COLOR_DEPTH (1 << 2) + /* offset 3 for HDMI */ + # define DP_DS_HDMI_FRAME_SEQ_TO_FRAME_PACK (1 << 0) + # define DP_DS_HDMI_YCBCR422_PASS_THROUGH (1 << 1) + # define DP_DS_HDMI_YCBCR420_PASS_THROUGH (1 << 2) + # define DP_DS_HDMI_YCBCR444_TO_422_CONV (1 << 3) + # define DP_DS_HDMI_YCBCR444_TO_420_CONV (1 << 4) + + /* + * VESA DP-to-HDMI PCON Specification adds caps for colorspace + * conversion in DFP cap DPCD 83h. Sec6.1 Table-3. + * Based on the available support the source can enable + * color conversion by writing into PROTOCOL_COVERTER_CONTROL_2 + * DPCD 3052h. + */ + # define DP_DS_HDMI_BT601_RGB_YCBCR_CONV (1 << 5) + # define DP_DS_HDMI_BT709_RGB_YCBCR_CONV (1 << 6) + # define DP_DS_HDMI_BT2020_RGB_YCBCR_CONV (1 << 7) + + #define DP_MAX_DOWNSTREAM_PORTS 0x10 + + /* DP Forward error Correction Registers */ + #define DP_FEC_CAPABILITY 0x090 /* 1.4 */ + # define DP_FEC_CAPABLE (1 << 0) + # define DP_FEC_UNCORR_BLK_ERROR_COUNT_CAP (1 << 1) + # define DP_FEC_CORR_BLK_ERROR_COUNT_CAP (1 << 2) + # define DP_FEC_BIT_ERROR_COUNT_CAP (1 << 3) + #define DP_FEC_CAPABILITY_1 0x091 /* 2.0 */ + + /* DP-HDMI2.1 PCON DSC ENCODER SUPPORT */ + #define DP_PCON_DSC_ENCODER_CAP_SIZE 0xC /* 0x9E - 0x92 */ + #define DP_PCON_DSC_ENCODER 0x092 + # define DP_PCON_DSC_ENCODER_SUPPORTED (1 << 0) + # define DP_PCON_DSC_PPS_ENC_OVERRIDE (1 << 1) + + /* DP-HDMI2.1 PCON DSC Version */ + #define DP_PCON_DSC_VERSION 0x093 + # define DP_PCON_DSC_MAJOR_MASK (0xF << 0) + # define DP_PCON_DSC_MINOR_MASK (0xF << 4) + # define DP_PCON_DSC_MAJOR_SHIFT 0 + # define DP_PCON_DSC_MINOR_SHIFT 4 + + /* DP-HDMI2.1 PCON DSC RC Buffer block size */ + #define DP_PCON_DSC_RC_BUF_BLK_INFO 0x094 + # define DP_PCON_DSC_RC_BUF_BLK_SIZE (0x3 << 0) + # define DP_PCON_DSC_RC_BUF_BLK_1KB 0 + # define DP_PCON_DSC_RC_BUF_BLK_4KB 1 + # define DP_PCON_DSC_RC_BUF_BLK_16KB 2 + # define DP_PCON_DSC_RC_BUF_BLK_64KB 3 + + /* DP-HDMI2.1 PCON DSC RC Buffer size */ + #define DP_PCON_DSC_RC_BUF_SIZE 0x095 + + /* DP-HDMI2.1 PCON DSC Slice capabilities-1 */ + #define DP_PCON_DSC_SLICE_CAP_1 0x096 + # define DP_PCON_DSC_1_PER_DSC_ENC (0x1 << 0) + # define DP_PCON_DSC_2_PER_DSC_ENC (0x1 << 1) + # define DP_PCON_DSC_4_PER_DSC_ENC (0x1 << 3) + # define DP_PCON_DSC_6_PER_DSC_ENC (0x1 << 4) + # define DP_PCON_DSC_8_PER_DSC_ENC (0x1 << 5) + # define DP_PCON_DSC_10_PER_DSC_ENC (0x1 << 6) + # define DP_PCON_DSC_12_PER_DSC_ENC (0x1 << 7) + + #define DP_PCON_DSC_BUF_BIT_DEPTH 0x097 + # define DP_PCON_DSC_BIT_DEPTH_MASK (0xF << 0) + # define DP_PCON_DSC_DEPTH_9_BITS 0 + # define DP_PCON_DSC_DEPTH_10_BITS 1 + # define DP_PCON_DSC_DEPTH_11_BITS 2 + # define DP_PCON_DSC_DEPTH_12_BITS 3 + # define DP_PCON_DSC_DEPTH_13_BITS 4 + # define DP_PCON_DSC_DEPTH_14_BITS 5 + # define DP_PCON_DSC_DEPTH_15_BITS 6 + # define DP_PCON_DSC_DEPTH_16_BITS 7 + # define DP_PCON_DSC_DEPTH_8_BITS 8 + + #define DP_PCON_DSC_BLOCK_PREDICTION 0x098 + # define DP_PCON_DSC_BLOCK_PRED_SUPPORT (0x1 << 0) + + #define DP_PCON_DSC_ENC_COLOR_FMT_CAP 0x099 + # define DP_PCON_DSC_ENC_RGB (0x1 << 0) + # define DP_PCON_DSC_ENC_YUV444 (0x1 << 1) + # define DP_PCON_DSC_ENC_YUV422_S (0x1 << 2) + # define DP_PCON_DSC_ENC_YUV422_N (0x1 << 3) + # define DP_PCON_DSC_ENC_YUV420_N (0x1 << 4) + + #define DP_PCON_DSC_ENC_COLOR_DEPTH_CAP 0x09A + # define DP_PCON_DSC_ENC_8BPC (0x1 << 1) + # define DP_PCON_DSC_ENC_10BPC (0x1 << 2) + # define DP_PCON_DSC_ENC_12BPC (0x1 << 3) + + #define DP_PCON_DSC_MAX_SLICE_WIDTH 0x09B + + /* DP-HDMI2.1 PCON DSC Slice capabilities-2 */ + #define DP_PCON_DSC_SLICE_CAP_2 0x09C + # define DP_PCON_DSC_16_PER_DSC_ENC (0x1 << 0) + # define DP_PCON_DSC_20_PER_DSC_ENC (0x1 << 1) + # define DP_PCON_DSC_24_PER_DSC_ENC (0x1 << 2) + + /* DP-HDMI2.1 PCON HDMI TX Encoder Bits/pixel increment */ + #define DP_PCON_DSC_BPP_INCR 0x09E + # define DP_PCON_DSC_BPP_INCR_MASK (0x7 << 0) + # define DP_PCON_DSC_ONE_16TH_BPP 0 + # define DP_PCON_DSC_ONE_8TH_BPP 1 + # define DP_PCON_DSC_ONE_4TH_BPP 2 + # define DP_PCON_DSC_ONE_HALF_BPP 3 + # define DP_PCON_DSC_ONE_BPP 4 + + /* DP Extended DSC Capabilities */ + #define DP_DSC_BRANCH_OVERALL_THROUGHPUT_0 0x0a0 /* DP 1.4a SCR */ + #define DP_DSC_BRANCH_OVERALL_THROUGHPUT_1 0x0a1 + #define DP_DSC_BRANCH_MAX_LINE_WIDTH 0x0a2 + + /* DFP Capability Extension */ + #define DP_DFP_CAPABILITY_EXTENSION_SUPPORT 0x0a3 /* 2.0 */ + + /* Link Configuration */ + #define DP_LINK_BW_SET 0x100 + # define DP_LINK_RATE_TABLE 0x00 /* eDP 1.4 */ + # define DP_LINK_BW_1_62 0x06 + # define DP_LINK_BW_2_7 0x0a + # define DP_LINK_BW_5_4 0x14 /* 1.2 */ + # define DP_LINK_BW_8_1 0x1e /* 1.4 */ + # define DP_LINK_BW_10 0x01 /* 2.0 128b/132b Link Layer */ + # define DP_LINK_BW_13_5 0x04 /* 2.0 128b/132b Link Layer */ + # define DP_LINK_BW_20 0x02 /* 2.0 128b/132b Link Layer */ + + #define DP_LANE_COUNT_SET 0x101 + # define DP_LANE_COUNT_MASK 0x0f + # define DP_LANE_COUNT_ENHANCED_FRAME_EN (1 << 7) + + #define DP_TRAINING_PATTERN_SET 0x102 + # define DP_TRAINING_PATTERN_DISABLE 0 + # define DP_TRAINING_PATTERN_1 1 + # define DP_TRAINING_PATTERN_2 2 + # define DP_TRAINING_PATTERN_3 3 /* 1.2 */ + # define DP_TRAINING_PATTERN_4 7 /* 1.4 */ + # define DP_TRAINING_PATTERN_MASK 0x3 + # define DP_TRAINING_PATTERN_MASK_1_4 0xf + + /* DPCD 1.1 only. For DPCD >= 1.2 see per-lane DP_LINK_QUAL_LANEn_SET */ + # define DP_LINK_QUAL_PATTERN_11_DISABLE (0 << 2) + # define DP_LINK_QUAL_PATTERN_11_D10_2 (1 << 2) + # define DP_LINK_QUAL_PATTERN_11_ERROR_RATE (2 << 2) + # define DP_LINK_QUAL_PATTERN_11_PRBS7 (3 << 2) + # define DP_LINK_QUAL_PATTERN_11_MASK (3 << 2) + + # define DP_RECOVERED_CLOCK_OUT_EN (1 << 4) + # define DP_LINK_SCRAMBLING_DISABLE (1 << 5) + + # define DP_SYMBOL_ERROR_COUNT_BOTH (0 << 6) + # define DP_SYMBOL_ERROR_COUNT_DISPARITY (1 << 6) + # define DP_SYMBOL_ERROR_COUNT_SYMBOL (2 << 6) + # define DP_SYMBOL_ERROR_COUNT_MASK (3 << 6) + + #define DP_TRAINING_LANE0_SET 0x103 + #define DP_TRAINING_LANE1_SET 0x104 + #define DP_TRAINING_LANE2_SET 0x105 + #define DP_TRAINING_LANE3_SET 0x106 + + # define DP_TRAIN_VOLTAGE_SWING_MASK 0x3 + # define DP_TRAIN_VOLTAGE_SWING_SHIFT 0 + # define DP_TRAIN_MAX_SWING_REACHED (1 << 2) + # define DP_TRAIN_VOLTAGE_SWING_LEVEL_0 (0 << 0) + # define DP_TRAIN_VOLTAGE_SWING_LEVEL_1 (1 << 0) + # define DP_TRAIN_VOLTAGE_SWING_LEVEL_2 (2 << 0) + # define DP_TRAIN_VOLTAGE_SWING_LEVEL_3 (3 << 0) + + # define DP_TRAIN_PRE_EMPHASIS_MASK (3 << 3) + # define DP_TRAIN_PRE_EMPH_LEVEL_0 (0 << 3) + # define DP_TRAIN_PRE_EMPH_LEVEL_1 (1 << 3) + # define DP_TRAIN_PRE_EMPH_LEVEL_2 (2 << 3) + # define DP_TRAIN_PRE_EMPH_LEVEL_3 (3 << 3) + + # define DP_TRAIN_PRE_EMPHASIS_SHIFT 3 + # define DP_TRAIN_MAX_PRE_EMPHASIS_REACHED (1 << 5) + + # define DP_TX_FFE_PRESET_VALUE_MASK (0xf << 0) /* 2.0 128b/132b Link Layer */ + + #define DP_DOWNSPREAD_CTRL 0x107 + # define DP_SPREAD_AMP_0_5 (1 << 4) + # define DP_MSA_TIMING_PAR_IGNORE_EN (1 << 7) /* eDP */ + + #define DP_MAIN_LINK_CHANNEL_CODING_SET 0x108 + # define DP_SET_ANSI_8B10B (1 << 0) + # define DP_SET_ANSI_128B132B (1 << 1) + + #define DP_I2C_SPEED_CONTROL_STATUS 0x109 /* DPI */ + /* bitmask as for DP_I2C_SPEED_CAP */ + + #define DP_EDP_CONFIGURATION_SET 0x10a /* XXX 1.2? */ + # define DP_ALTERNATE_SCRAMBLER_RESET_ENABLE (1 << 0) + # define DP_FRAMING_CHANGE_ENABLE (1 << 1) + # define DP_PANEL_SELF_TEST_ENABLE (1 << 7) + + #define DP_LINK_QUAL_LANE0_SET 0x10b /* DPCD >= 1.2 */ + #define DP_LINK_QUAL_LANE1_SET 0x10c + #define DP_LINK_QUAL_LANE2_SET 0x10d + #define DP_LINK_QUAL_LANE3_SET 0x10e + # define DP_LINK_QUAL_PATTERN_DISABLE 0 + # define DP_LINK_QUAL_PATTERN_D10_2 1 + # define DP_LINK_QUAL_PATTERN_ERROR_RATE 2 + # define DP_LINK_QUAL_PATTERN_PRBS7 3 + # define DP_LINK_QUAL_PATTERN_80BIT_CUSTOM 4 + # define DP_LINK_QUAL_PATTERN_CP2520_PAT_1 5 + # define DP_LINK_QUAL_PATTERN_CP2520_PAT_2 6 + # define DP_LINK_QUAL_PATTERN_CP2520_PAT_3 7 + /* DP 2.0 UHBR10, UHBR13.5, UHBR20 */ + # define DP_LINK_QUAL_PATTERN_128B132B_TPS1 0x08 + # define DP_LINK_QUAL_PATTERN_128B132B_TPS2 0x10 + # define DP_LINK_QUAL_PATTERN_PRSBS9 0x18 + # define DP_LINK_QUAL_PATTERN_PRSBS11 0x20 + # define DP_LINK_QUAL_PATTERN_PRSBS15 0x28 + # define DP_LINK_QUAL_PATTERN_PRSBS23 0x30 + # define DP_LINK_QUAL_PATTERN_PRSBS31 0x38 + # define DP_LINK_QUAL_PATTERN_CUSTOM 0x40 + # define DP_LINK_QUAL_PATTERN_SQUARE 0x48 + + #define DP_TRAINING_LANE0_1_SET2 0x10f + #define DP_TRAINING_LANE2_3_SET2 0x110 + # define DP_LANE02_POST_CURSOR2_SET_MASK (3 << 0) + # define DP_LANE02_MAX_POST_CURSOR2_REACHED (1 << 2) + # define DP_LANE13_POST_CURSOR2_SET_MASK (3 << 4) + # define DP_LANE13_MAX_POST_CURSOR2_REACHED (1 << 6) + + #define DP_MSTM_CTRL 0x111 /* 1.2 */ + # define DP_MST_EN (1 << 0) + # define DP_UP_REQ_EN (1 << 1) + # define DP_UPSTREAM_IS_SRC (1 << 2) + + #define DP_AUDIO_DELAY0 0x112 /* 1.2 */ + #define DP_AUDIO_DELAY1 0x113 + #define DP_AUDIO_DELAY2 0x114 + + #define DP_LINK_RATE_SET 0x115 /* eDP 1.4 */ + # define DP_LINK_RATE_SET_SHIFT 0 + # define DP_LINK_RATE_SET_MASK (7 << 0) + + #define DP_RECEIVER_ALPM_CONFIG 0x116 /* eDP 1.4 */ + # define DP_ALPM_ENABLE (1 << 0) + # define DP_ALPM_LOCK_ERROR_IRQ_HPD_ENABLE (1 << 1) + + #define DP_SINK_DEVICE_AUX_FRAME_SYNC_CONF 0x117 /* eDP 1.4 */ + # define DP_AUX_FRAME_SYNC_ENABLE (1 << 0) + # define DP_IRQ_HPD_ENABLE (1 << 1) + + #define DP_UPSTREAM_DEVICE_DP_PWR_NEED 0x118 /* 1.2 */ + # define DP_PWR_NOT_NEEDED (1 << 0) + + #define DP_FEC_CONFIGURATION 0x120 /* 1.4 */ + # define DP_FEC_READY (1 << 0) + # define DP_FEC_ERR_COUNT_SEL_MASK (7 << 1) + # define DP_FEC_ERR_COUNT_DIS (0 << 1) + # define DP_FEC_UNCORR_BLK_ERROR_COUNT (1 << 1) + # define DP_FEC_CORR_BLK_ERROR_COUNT (2 << 1) + # define DP_FEC_BIT_ERROR_COUNT (3 << 1) + # define DP_FEC_LANE_SELECT_MASK (3 << 4) + # define DP_FEC_LANE_0_SELECT (0 << 4) + # define DP_FEC_LANE_1_SELECT (1 << 4) + # define DP_FEC_LANE_2_SELECT (2 << 4) + # define DP_FEC_LANE_3_SELECT (3 << 4) + + #define DP_AUX_FRAME_SYNC_VALUE 0x15c /* eDP 1.4 */ + # define DP_AUX_FRAME_SYNC_VALID (1 << 0) + + #define DP_DSC_ENABLE 0x160 /* DP 1.4 */ + # define DP_DECOMPRESSION_EN (1 << 0) + #define DP_DSC_CONFIGURATION 0x161 /* DP 2.0 */ + + #define DP_PSR_EN_CFG 0x170 /* XXX 1.2? */ + # define DP_PSR_ENABLE BIT(0) + # define DP_PSR_MAIN_LINK_ACTIVE BIT(1) + # define DP_PSR_CRC_VERIFICATION BIT(2) + # define DP_PSR_FRAME_CAPTURE BIT(3) + # define DP_PSR_SU_REGION_SCANLINE_CAPTURE BIT(4) /* eDP 1.4a */ + # define DP_PSR_IRQ_HPD_WITH_CRC_ERRORS BIT(5) /* eDP 1.4a */ + # define DP_PSR_ENABLE_PSR2 BIT(6) /* eDP 1.4a */ + + #define DP_ADAPTER_CTRL 0x1a0 + # define DP_ADAPTER_CTRL_FORCE_LOAD_SENSE (1 << 0) + + #define DP_BRANCH_DEVICE_CTRL 0x1a1 + # define DP_BRANCH_DEVICE_IRQ_HPD (1 << 0) + + #define DP_PAYLOAD_ALLOCATE_SET 0x1c0 + #define DP_PAYLOAD_ALLOCATE_START_TIME_SLOT 0x1c1 + #define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT 0x1c2 + + /* Link/Sink Device Status */ + #define DP_SINK_COUNT 0x200 + /* prior to 1.2 bit 7 was reserved mbz */ + # define DP_GET_SINK_COUNT(x) ((((x) & 0x80) >> 1) | ((x) & 0x3f)) + # define DP_SINK_CP_READY (1 << 6) + + #define DP_DEVICE_SERVICE_IRQ_VECTOR 0x201 + # define DP_REMOTE_CONTROL_COMMAND_PENDING (1 << 0) + # define DP_AUTOMATED_TEST_REQUEST (1 << 1) + # define DP_CP_IRQ (1 << 2) + # define DP_MCCS_IRQ (1 << 3) + # define DP_DOWN_REP_MSG_RDY (1 << 4) /* 1.2 MST */ + # define DP_UP_REQ_MSG_RDY (1 << 5) /* 1.2 MST */ + # define DP_SINK_SPECIFIC_IRQ (1 << 6) + + #define DP_LANE0_1_STATUS 0x202 + #define DP_LANE2_3_STATUS 0x203 + # define DP_LANE_CR_DONE (1 << 0) + # define DP_LANE_CHANNEL_EQ_DONE (1 << 1) + # define DP_LANE_SYMBOL_LOCKED (1 << 2) + + #define DP_CHANNEL_EQ_BITS (DP_LANE_CR_DONE | \ + DP_LANE_CHANNEL_EQ_DONE | \ + DP_LANE_SYMBOL_LOCKED) + + #define DP_LANE_ALIGN_STATUS_UPDATED 0x204 + + #define DP_INTERLANE_ALIGN_DONE (1 << 0) + #define DP_DOWNSTREAM_PORT_STATUS_CHANGED (1 << 6) + #define DP_LINK_STATUS_UPDATED (1 << 7) + + #define DP_SINK_STATUS 0x205 + # define DP_RECEIVE_PORT_0_STATUS (1 << 0) + # define DP_RECEIVE_PORT_1_STATUS (1 << 1) + # define DP_STREAM_REGENERATION_STATUS (1 << 2) /* 2.0 */ + # define DP_INTRA_HOP_AUX_REPLY_INDICATION (1 << 3) /* 2.0 */ + + #define DP_ADJUST_REQUEST_LANE0_1 0x206 + #define DP_ADJUST_REQUEST_LANE2_3 0x207 + # define DP_ADJUST_VOLTAGE_SWING_LANE0_MASK 0x03 + # define DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT 0 + # define DP_ADJUST_PRE_EMPHASIS_LANE0_MASK 0x0c + # define DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT 2 + # define DP_ADJUST_VOLTAGE_SWING_LANE1_MASK 0x30 + # define DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT 4 + # define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK 0xc0 + # define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT 6 + + /* DP 2.0 128b/132b Link Layer */ + # define DP_ADJUST_TX_FFE_PRESET_LANE0_MASK (0xf << 0) + # define DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT 0 + # define DP_ADJUST_TX_FFE_PRESET_LANE1_MASK (0xf << 4) + # define DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT 4 + + #define DP_ADJUST_REQUEST_POST_CURSOR2 0x20c + # define DP_ADJUST_POST_CURSOR2_LANE0_MASK 0x03 + # define DP_ADJUST_POST_CURSOR2_LANE0_SHIFT 0 + # define DP_ADJUST_POST_CURSOR2_LANE1_MASK 0x0c + # define DP_ADJUST_POST_CURSOR2_LANE1_SHIFT 2 + # define DP_ADJUST_POST_CURSOR2_LANE2_MASK 0x30 + # define DP_ADJUST_POST_CURSOR2_LANE2_SHIFT 4 + # define DP_ADJUST_POST_CURSOR2_LANE3_MASK 0xc0 + # define DP_ADJUST_POST_CURSOR2_LANE3_SHIFT 6 + + #define DP_TEST_REQUEST 0x218 + # define DP_TEST_LINK_TRAINING (1 << 0) + # define DP_TEST_LINK_VIDEO_PATTERN (1 << 1) + # define DP_TEST_LINK_EDID_READ (1 << 2) + # define DP_TEST_LINK_PHY_TEST_PATTERN (1 << 3) /* DPCD >= 1.1 */ + # define DP_TEST_LINK_FAUX_PATTERN (1 << 4) /* DPCD >= 1.2 */ + # define DP_TEST_LINK_AUDIO_PATTERN (1 << 5) /* DPCD >= 1.2 */ + # define DP_TEST_LINK_AUDIO_DISABLED_VIDEO (1 << 6) /* DPCD >= 1.2 */ + + #define DP_TEST_LINK_RATE 0x219 + # define DP_LINK_RATE_162 (0x6) + # define DP_LINK_RATE_27 (0xa) + + #define DP_TEST_LANE_COUNT 0x220 + + #define DP_TEST_PATTERN 0x221 + # define DP_NO_TEST_PATTERN 0x0 + # define DP_COLOR_RAMP 0x1 + # define DP_BLACK_AND_WHITE_VERTICAL_LINES 0x2 + # define DP_COLOR_SQUARE 0x3 + + #define DP_TEST_H_TOTAL_HI 0x222 + #define DP_TEST_H_TOTAL_LO 0x223 + + #define DP_TEST_V_TOTAL_HI 0x224 + #define DP_TEST_V_TOTAL_LO 0x225 + + #define DP_TEST_H_START_HI 0x226 + #define DP_TEST_H_START_LO 0x227 + + #define DP_TEST_V_START_HI 0x228 + #define DP_TEST_V_START_LO 0x229 + + #define DP_TEST_HSYNC_HI 0x22A + # define DP_TEST_HSYNC_POLARITY (1 << 7) + # define DP_TEST_HSYNC_WIDTH_HI_MASK (127 << 0) + #define DP_TEST_HSYNC_WIDTH_LO 0x22B + + #define DP_TEST_VSYNC_HI 0x22C + # define DP_TEST_VSYNC_POLARITY (1 << 7) + # define DP_TEST_VSYNC_WIDTH_HI_MASK (127 << 0) + #define DP_TEST_VSYNC_WIDTH_LO 0x22D + + #define DP_TEST_H_WIDTH_HI 0x22E + #define DP_TEST_H_WIDTH_LO 0x22F + + #define DP_TEST_V_HEIGHT_HI 0x230 + #define DP_TEST_V_HEIGHT_LO 0x231 + + #define DP_TEST_MISC0 0x232 + # define DP_TEST_SYNC_CLOCK (1 << 0) + # define DP_TEST_COLOR_FORMAT_MASK (3 << 1) + # define DP_TEST_COLOR_FORMAT_SHIFT 1 + # define DP_COLOR_FORMAT_RGB (0 << 1) + # define DP_COLOR_FORMAT_YCbCr422 (1 << 1) + # define DP_COLOR_FORMAT_YCbCr444 (2 << 1) + # define DP_TEST_DYNAMIC_RANGE_VESA (0 << 3) + # define DP_TEST_DYNAMIC_RANGE_CEA (1 << 3) + # define DP_TEST_YCBCR_COEFFICIENTS (1 << 4) + # define DP_YCBCR_COEFFICIENTS_ITU601 (0 << 4) + # define DP_YCBCR_COEFFICIENTS_ITU709 (1 << 4) + # define DP_TEST_BIT_DEPTH_MASK (7 << 5) + # define DP_TEST_BIT_DEPTH_SHIFT 5 + # define DP_TEST_BIT_DEPTH_6 (0 << 5) + # define DP_TEST_BIT_DEPTH_8 (1 << 5) + # define DP_TEST_BIT_DEPTH_10 (2 << 5) + # define DP_TEST_BIT_DEPTH_12 (3 << 5) + # define DP_TEST_BIT_DEPTH_16 (4 << 5) + + #define DP_TEST_MISC1 0x233 + # define DP_TEST_REFRESH_DENOMINATOR (1 << 0) + # define DP_TEST_INTERLACED (1 << 1) + + #define DP_TEST_REFRESH_RATE_NUMERATOR 0x234 + + #define DP_TEST_MISC0 0x232 + + #define DP_TEST_CRC_R_CR 0x240 + #define DP_TEST_CRC_G_Y 0x242 + #define DP_TEST_CRC_B_CB 0x244 + + #define DP_TEST_SINK_MISC 0x246 + # define DP_TEST_CRC_SUPPORTED (1 << 5) + # define DP_TEST_COUNT_MASK 0xf + + #define DP_PHY_TEST_PATTERN 0x248 + # define DP_PHY_TEST_PATTERN_SEL_MASK 0x7 + # define DP_PHY_TEST_PATTERN_NONE 0x0 + # define DP_PHY_TEST_PATTERN_D10_2 0x1 + # define DP_PHY_TEST_PATTERN_ERROR_COUNT 0x2 + # define DP_PHY_TEST_PATTERN_PRBS7 0x3 + # define DP_PHY_TEST_PATTERN_80BIT_CUSTOM 0x4 + # define DP_PHY_TEST_PATTERN_CP2520 0x5 + + #define DP_PHY_SQUARE_PATTERN 0x249 + + #define DP_TEST_HBR2_SCRAMBLER_RESET 0x24A + #define DP_TEST_80BIT_CUSTOM_PATTERN_7_0 0x250 + #define DP_TEST_80BIT_CUSTOM_PATTERN_15_8 0x251 + #define DP_TEST_80BIT_CUSTOM_PATTERN_23_16 0x252 + #define DP_TEST_80BIT_CUSTOM_PATTERN_31_24 0x253 + #define DP_TEST_80BIT_CUSTOM_PATTERN_39_32 0x254 + #define DP_TEST_80BIT_CUSTOM_PATTERN_47_40 0x255 + #define DP_TEST_80BIT_CUSTOM_PATTERN_55_48 0x256 + #define DP_TEST_80BIT_CUSTOM_PATTERN_63_56 0x257 + #define DP_TEST_80BIT_CUSTOM_PATTERN_71_64 0x258 + #define DP_TEST_80BIT_CUSTOM_PATTERN_79_72 0x259 + + #define DP_TEST_RESPONSE 0x260 + # define DP_TEST_ACK (1 << 0) + # define DP_TEST_NAK (1 << 1) + # define DP_TEST_EDID_CHECKSUM_WRITE (1 << 2) + + #define DP_TEST_EDID_CHECKSUM 0x261 + + #define DP_TEST_SINK 0x270 + # define DP_TEST_SINK_START (1 << 0) + #define DP_TEST_AUDIO_MODE 0x271 + #define DP_TEST_AUDIO_PATTERN_TYPE 0x272 + #define DP_TEST_AUDIO_PERIOD_CH1 0x273 + #define DP_TEST_AUDIO_PERIOD_CH2 0x274 + #define DP_TEST_AUDIO_PERIOD_CH3 0x275 + #define DP_TEST_AUDIO_PERIOD_CH4 0x276 + #define DP_TEST_AUDIO_PERIOD_CH5 0x277 + #define DP_TEST_AUDIO_PERIOD_CH6 0x278 + #define DP_TEST_AUDIO_PERIOD_CH7 0x279 + #define DP_TEST_AUDIO_PERIOD_CH8 0x27A + + #define DP_FEC_STATUS 0x280 /* 1.4 */ + # define DP_FEC_DECODE_EN_DETECTED (1 << 0) + # define DP_FEC_DECODE_DIS_DETECTED (1 << 1) + + #define DP_FEC_ERROR_COUNT_LSB 0x0281 /* 1.4 */ + + #define DP_FEC_ERROR_COUNT_MSB 0x0282 /* 1.4 */ + # define DP_FEC_ERROR_COUNT_MASK 0x7F + # define DP_FEC_ERR_COUNT_VALID (1 << 7) + + #define DP_PAYLOAD_TABLE_UPDATE_STATUS 0x2c0 /* 1.2 MST */ + # define DP_PAYLOAD_TABLE_UPDATED (1 << 0) + # define DP_PAYLOAD_ACT_HANDLED (1 << 1) + + #define DP_VC_PAYLOAD_ID_SLOT_1 0x2c1 /* 1.2 MST */ + /* up to ID_SLOT_63 at 0x2ff */ + + /* Source Device-specific */ + #define DP_SOURCE_OUI 0x300 + + /* Sink Device-specific */ + #define DP_SINK_OUI 0x400 + + /* Branch Device-specific */ + #define DP_BRANCH_OUI 0x500 + #define DP_BRANCH_ID 0x503 + #define DP_BRANCH_REVISION_START 0x509 + #define DP_BRANCH_HW_REV 0x509 + #define DP_BRANCH_SW_REV 0x50A + + /* Link/Sink Device Power Control */ + #define DP_SET_POWER 0x600 + # define DP_SET_POWER_D0 0x1 + # define DP_SET_POWER_D3 0x2 + # define DP_SET_POWER_MASK 0x3 + # define DP_SET_POWER_D3_AUX_ON 0x5 + + /* eDP-specific */ + #define DP_EDP_DPCD_REV 0x700 /* eDP 1.2 */ + # define DP_EDP_11 0x00 + # define DP_EDP_12 0x01 + # define DP_EDP_13 0x02 + # define DP_EDP_14 0x03 + # define DP_EDP_14a 0x04 /* eDP 1.4a */ + # define DP_EDP_14b 0x05 /* eDP 1.4b */ + + #define DP_EDP_GENERAL_CAP_1 0x701 + # define DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP (1 << 0) + # define DP_EDP_BACKLIGHT_PIN_ENABLE_CAP (1 << 1) + # define DP_EDP_BACKLIGHT_AUX_ENABLE_CAP (1 << 2) + # define DP_EDP_PANEL_SELF_TEST_PIN_ENABLE_CAP (1 << 3) + # define DP_EDP_PANEL_SELF_TEST_AUX_ENABLE_CAP (1 << 4) + # define DP_EDP_FRC_ENABLE_CAP (1 << 5) + # define DP_EDP_COLOR_ENGINE_CAP (1 << 6) + # define DP_EDP_SET_POWER_CAP (1 << 7) + + #define DP_EDP_BACKLIGHT_ADJUSTMENT_CAP 0x702 + # define DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP (1 << 0) + # define DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP (1 << 1) + # define DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT (1 << 2) + # define DP_EDP_BACKLIGHT_AUX_PWM_PRODUCT_CAP (1 << 3) + # define DP_EDP_BACKLIGHT_FREQ_PWM_PIN_PASSTHRU_CAP (1 << 4) + # define DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP (1 << 5) + # define DP_EDP_DYNAMIC_BACKLIGHT_CAP (1 << 6) + # define DP_EDP_VBLANK_BACKLIGHT_UPDATE_CAP (1 << 7) + + #define DP_EDP_GENERAL_CAP_2 0x703 + # define DP_EDP_OVERDRIVE_ENGINE_ENABLED (1 << 0) + + #define DP_EDP_GENERAL_CAP_3 0x704 /* eDP 1.4 */ + # define DP_EDP_X_REGION_CAP_MASK (0xf << 0) + # define DP_EDP_X_REGION_CAP_SHIFT 0 + # define DP_EDP_Y_REGION_CAP_MASK (0xf << 4) + # define DP_EDP_Y_REGION_CAP_SHIFT 4 + + #define DP_EDP_DISPLAY_CONTROL_REGISTER 0x720 + # define DP_EDP_BACKLIGHT_ENABLE (1 << 0) + # define DP_EDP_BLACK_VIDEO_ENABLE (1 << 1) + # define DP_EDP_FRC_ENABLE (1 << 2) + # define DP_EDP_COLOR_ENGINE_ENABLE (1 << 3) + # define DP_EDP_VBLANK_BACKLIGHT_UPDATE_ENABLE (1 << 7) + + #define DP_EDP_BACKLIGHT_MODE_SET_REGISTER 0x721 + # define DP_EDP_BACKLIGHT_CONTROL_MODE_MASK (3 << 0) + # define DP_EDP_BACKLIGHT_CONTROL_MODE_PWM (0 << 0) + # define DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET (1 << 0) + # define DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD (2 << 0) + # define DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT (3 << 0) + # define DP_EDP_BACKLIGHT_FREQ_PWM_PIN_PASSTHRU_ENABLE (1 << 2) + # define DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE (1 << 3) + # define DP_EDP_DYNAMIC_BACKLIGHT_ENABLE (1 << 4) + # define DP_EDP_REGIONAL_BACKLIGHT_ENABLE (1 << 5) + # define DP_EDP_UPDATE_REGION_BRIGHTNESS (1 << 6) /* eDP 1.4 */ + + #define DP_EDP_BACKLIGHT_BRIGHTNESS_MSB 0x722 + #define DP_EDP_BACKLIGHT_BRIGHTNESS_LSB 0x723 + + #define DP_EDP_PWMGEN_BIT_COUNT 0x724 + #define DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN 0x725 + #define DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX 0x726 + # define DP_EDP_PWMGEN_BIT_COUNT_MASK (0x1f << 0) + + #define DP_EDP_BACKLIGHT_CONTROL_STATUS 0x727 + + #define DP_EDP_BACKLIGHT_FREQ_SET 0x728 + # define DP_EDP_BACKLIGHT_FREQ_BASE_KHZ 27000 + + #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_MSB 0x72a + #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_MID 0x72b + #define DP_EDP_BACKLIGHT_FREQ_CAP_MIN_LSB 0x72c + + #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_MSB 0x72d + #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_MID 0x72e + #define DP_EDP_BACKLIGHT_FREQ_CAP_MAX_LSB 0x72f + + #define DP_EDP_DBC_MINIMUM_BRIGHTNESS_SET 0x732 + #define DP_EDP_DBC_MAXIMUM_BRIGHTNESS_SET 0x733 + + #define DP_EDP_REGIONAL_BACKLIGHT_BASE 0x740 /* eDP 1.4 */ + #define DP_EDP_REGIONAL_BACKLIGHT_0 0x741 /* eDP 1.4 */ + + #define DP_EDP_MSO_LINK_CAPABILITIES 0x7a4 /* eDP 1.4 */ + # define DP_EDP_MSO_NUMBER_OF_LINKS_MASK (7 << 0) + # define DP_EDP_MSO_NUMBER_OF_LINKS_SHIFT 0 + # define DP_EDP_MSO_INDEPENDENT_LINK_BIT (1 << 3) + + /* Sideband MSG Buffers */ + #define DP_SIDEBAND_MSG_DOWN_REQ_BASE 0x1000 /* 1.2 MST */ + #define DP_SIDEBAND_MSG_UP_REP_BASE 0x1200 /* 1.2 MST */ + #define DP_SIDEBAND_MSG_DOWN_REP_BASE 0x1400 /* 1.2 MST */ + #define DP_SIDEBAND_MSG_UP_REQ_BASE 0x1600 /* 1.2 MST */ + + /* DPRX Event Status Indicator */ + #define DP_SINK_COUNT_ESI 0x2002 /* same as 0x200 */ + #define DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 0x2003 /* same as 0x201 */ + + #define DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1 0x2004 /* 1.2 */ + # define DP_RX_GTC_MSTR_REQ_STATUS_CHANGE (1 << 0) + # define DP_LOCK_ACQUISITION_REQUEST (1 << 1) + # define DP_CEC_IRQ (1 << 2) + + #define DP_LINK_SERVICE_IRQ_VECTOR_ESI0 0x2005 /* 1.2 */ + # define RX_CAP_CHANGED (1 << 0) + # define LINK_STATUS_CHANGED (1 << 1) + # define STREAM_STATUS_CHANGED (1 << 2) + # define HDMI_LINK_STATUS_CHANGED (1 << 3) + # define CONNECTED_OFF_ENTRY_REQUESTED (1 << 4) + + #define DP_PSR_ERROR_STATUS 0x2006 /* XXX 1.2? */ + # define DP_PSR_LINK_CRC_ERROR (1 << 0) + # define DP_PSR_RFB_STORAGE_ERROR (1 << 1) + # define DP_PSR_VSC_SDP_UNCORRECTABLE_ERROR (1 << 2) /* eDP 1.4 */ + + #define DP_PSR_ESI 0x2007 /* XXX 1.2? */ + # define DP_PSR_CAPS_CHANGE (1 << 0) + + #define DP_PSR_STATUS 0x2008 /* XXX 1.2? */ + # define DP_PSR_SINK_INACTIVE 0 + # define DP_PSR_SINK_ACTIVE_SRC_SYNCED 1 + # define DP_PSR_SINK_ACTIVE_RFB 2 + # define DP_PSR_SINK_ACTIVE_SINK_SYNCED 3 + # define DP_PSR_SINK_ACTIVE_RESYNC 4 + # define DP_PSR_SINK_INTERNAL_ERROR 7 + # define DP_PSR_SINK_STATE_MASK 0x07 + + #define DP_SYNCHRONIZATION_LATENCY_IN_SINK 0x2009 /* edp 1.4 */ + # define DP_MAX_RESYNC_FRAME_COUNT_MASK (0xf << 0) + # define DP_MAX_RESYNC_FRAME_COUNT_SHIFT 0 + # define DP_LAST_ACTUAL_SYNCHRONIZATION_LATENCY_MASK (0xf << 4) + # define DP_LAST_ACTUAL_SYNCHRONIZATION_LATENCY_SHIFT 4 + + #define DP_LAST_RECEIVED_PSR_SDP 0x200a /* eDP 1.2 */ + # define DP_PSR_STATE_BIT (1 << 0) /* eDP 1.2 */ + # define DP_UPDATE_RFB_BIT (1 << 1) /* eDP 1.2 */ + # define DP_CRC_VALID_BIT (1 << 2) /* eDP 1.2 */ + # define DP_SU_VALID (1 << 3) /* eDP 1.4 */ + # define DP_FIRST_SCAN_LINE_SU_REGION (1 << 4) /* eDP 1.4 */ + # define DP_LAST_SCAN_LINE_SU_REGION (1 << 5) /* eDP 1.4 */ + # define DP_Y_COORDINATE_VALID (1 << 6) /* eDP 1.4a */ + + #define DP_RECEIVER_ALPM_STATUS 0x200b /* eDP 1.4 */ + # define DP_ALPM_LOCK_TIMEOUT_ERROR (1 << 0) + + #define DP_LANE0_1_STATUS_ESI 0x200c /* status same as 0x202 */ + #define DP_LANE2_3_STATUS_ESI 0x200d /* status same as 0x203 */ + #define DP_LANE_ALIGN_STATUS_UPDATED_ESI 0x200e /* status same as 0x204 */ + #define DP_SINK_STATUS_ESI 0x200f /* status same as 0x205 */ + + /* Extended Receiver Capability: See DP_DPCD_REV for definitions */ + #define DP_DP13_DPCD_REV 0x2200 + + #define DP_DPRX_FEATURE_ENUMERATION_LIST 0x2210 /* DP 1.3 */ + # define DP_GTC_CAP (1 << 0) /* DP 1.3 */ + # define DP_SST_SPLIT_SDP_CAP (1 << 1) /* DP 1.4 */ + # define DP_AV_SYNC_CAP (1 << 2) /* DP 1.3 */ + # define DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED (1 << 3) /* DP 1.3 */ + # define DP_VSC_EXT_VESA_SDP_SUPPORTED (1 << 4) /* DP 1.4 */ + # define DP_VSC_EXT_VESA_SDP_CHAINING_SUPPORTED (1 << 5) /* DP 1.4 */ + # define DP_VSC_EXT_CEA_SDP_SUPPORTED (1 << 6) /* DP 1.4 */ + # define DP_VSC_EXT_CEA_SDP_CHAINING_SUPPORTED (1 << 7) /* DP 1.4 */ + + #define DP_128B132B_SUPPORTED_LINK_RATES 0x2215 /* 2.0 */ + # define DP_UHBR10 (1 << 0) + # define DP_UHBR20 (1 << 1) + # define DP_UHBR13_5 (1 << 2) + + #define DP_128B132B_TRAINING_AUX_RD_INTERVAL 0x2216 /* 2.0 */ + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK 0x7f + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US 0x00 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS 0x01 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS 0x02 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS 0x03 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS 0x04 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS 0x05 + # define DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS 0x06 + + #define DP_TEST_264BIT_CUSTOM_PATTERN_7_0 0x2230 + #define DP_TEST_264BIT_CUSTOM_PATTERN_263_256 0x2250 + + /* DSC Extended Capability Branch Total DSC Resources */ + #define DP_DSC_SUPPORT_AND_DSC_DECODER_COUNT 0x2260 /* 2.0 */ + # define DP_DSC_DECODER_COUNT_MASK (0b111 << 5) + # define DP_DSC_DECODER_COUNT_SHIFT 5 + #define DP_DSC_MAX_SLICE_COUNT_AND_AGGREGATION_0 0x2270 /* 2.0 */ + # define DP_DSC_DECODER_0_MAXIMUM_SLICE_COUNT_MASK (1 << 0) + # define DP_DSC_DECODER_0_AGGREGATION_SUPPORT_MASK (0b111 << 1) + # define DP_DSC_DECODER_0_AGGREGATION_SUPPORT_SHIFT 1 + + /* Protocol Converter Extension */ + /* HDMI CEC tunneling over AUX DP 1.3 section 5.3.3.3.1 DPCD 1.4+ */ + #define DP_CEC_TUNNELING_CAPABILITY 0x3000 + # define DP_CEC_TUNNELING_CAPABLE (1 << 0) + # define DP_CEC_SNOOPING_CAPABLE (1 << 1) + # define DP_CEC_MULTIPLE_LA_CAPABLE (1 << 2) + + #define DP_CEC_TUNNELING_CONTROL 0x3001 + # define DP_CEC_TUNNELING_ENABLE (1 << 0) + # define DP_CEC_SNOOPING_ENABLE (1 << 1) + + #define DP_CEC_RX_MESSAGE_INFO 0x3002 + # define DP_CEC_RX_MESSAGE_LEN_MASK (0xf << 0) + # define DP_CEC_RX_MESSAGE_LEN_SHIFT 0 + # define DP_CEC_RX_MESSAGE_HPD_STATE (1 << 4) + # define DP_CEC_RX_MESSAGE_HPD_LOST (1 << 5) + # define DP_CEC_RX_MESSAGE_ACKED (1 << 6) + # define DP_CEC_RX_MESSAGE_ENDED (1 << 7) + + #define DP_CEC_TX_MESSAGE_INFO 0x3003 + # define DP_CEC_TX_MESSAGE_LEN_MASK (0xf << 0) + # define DP_CEC_TX_MESSAGE_LEN_SHIFT 0 + # define DP_CEC_TX_RETRY_COUNT_MASK (0x7 << 4) + # define DP_CEC_TX_RETRY_COUNT_SHIFT 4 + # define DP_CEC_TX_MESSAGE_SEND (1 << 7) + + #define DP_CEC_TUNNELING_IRQ_FLAGS 0x3004 + # define DP_CEC_RX_MESSAGE_INFO_VALID (1 << 0) + # define DP_CEC_RX_MESSAGE_OVERFLOW (1 << 1) + # define DP_CEC_TX_MESSAGE_SENT (1 << 4) + # define DP_CEC_TX_LINE_ERROR (1 << 5) + # define DP_CEC_TX_ADDRESS_NACK_ERROR (1 << 6) + # define DP_CEC_TX_DATA_NACK_ERROR (1 << 7) + + #define DP_CEC_LOGICAL_ADDRESS_MASK 0x300E /* 0x300F word */ + # define DP_CEC_LOGICAL_ADDRESS_0 (1 << 0) + # define DP_CEC_LOGICAL_ADDRESS_1 (1 << 1) + # define DP_CEC_LOGICAL_ADDRESS_2 (1 << 2) + # define DP_CEC_LOGICAL_ADDRESS_3 (1 << 3) + # define DP_CEC_LOGICAL_ADDRESS_4 (1 << 4) + # define DP_CEC_LOGICAL_ADDRESS_5 (1 << 5) + # define DP_CEC_LOGICAL_ADDRESS_6 (1 << 6) + # define DP_CEC_LOGICAL_ADDRESS_7 (1 << 7) + #define DP_CEC_LOGICAL_ADDRESS_MASK_2 0x300F /* 0x300E word */ + # define DP_CEC_LOGICAL_ADDRESS_8 (1 << 0) + # define DP_CEC_LOGICAL_ADDRESS_9 (1 << 1) + # define DP_CEC_LOGICAL_ADDRESS_10 (1 << 2) + # define DP_CEC_LOGICAL_ADDRESS_11 (1 << 3) + # define DP_CEC_LOGICAL_ADDRESS_12 (1 << 4) + # define DP_CEC_LOGICAL_ADDRESS_13 (1 << 5) + # define DP_CEC_LOGICAL_ADDRESS_14 (1 << 6) + # define DP_CEC_LOGICAL_ADDRESS_15 (1 << 7) + + #define DP_CEC_RX_MESSAGE_BUFFER 0x3010 + #define DP_CEC_TX_MESSAGE_BUFFER 0x3020 + #define DP_CEC_MESSAGE_BUFFER_LENGTH 0x10 + + /* PCON CONFIGURE-1 FRL FOR HDMI SINK */ + #define DP_PCON_HDMI_LINK_CONFIG_1 0x305A + # define DP_PCON_ENABLE_MAX_FRL_BW (7 << 0) + # define DP_PCON_ENABLE_MAX_BW_0GBPS 0 + # define DP_PCON_ENABLE_MAX_BW_9GBPS 1 + # define DP_PCON_ENABLE_MAX_BW_18GBPS 2 + # define DP_PCON_ENABLE_MAX_BW_24GBPS 3 + # define DP_PCON_ENABLE_MAX_BW_32GBPS 4 + # define DP_PCON_ENABLE_MAX_BW_40GBPS 5 + # define DP_PCON_ENABLE_MAX_BW_48GBPS 6 + # define DP_PCON_ENABLE_SOURCE_CTL_MODE (1 << 3) + # define DP_PCON_ENABLE_CONCURRENT_LINK (1 << 4) + # define DP_PCON_ENABLE_SEQUENTIAL_LINK (0 << 4) + # define DP_PCON_ENABLE_LINK_FRL_MODE (1 << 5) + # define DP_PCON_ENABLE_HPD_READY (1 << 6) + # define DP_PCON_ENABLE_HDMI_LINK (1 << 7) + + /* PCON CONFIGURE-2 FRL FOR HDMI SINK */ + #define DP_PCON_HDMI_LINK_CONFIG_2 0x305B + # define DP_PCON_MAX_LINK_BW_MASK (0x3F << 0) + # define DP_PCON_FRL_BW_MASK_9GBPS (1 << 0) + # define DP_PCON_FRL_BW_MASK_18GBPS (1 << 1) + # define DP_PCON_FRL_BW_MASK_24GBPS (1 << 2) + # define DP_PCON_FRL_BW_MASK_32GBPS (1 << 3) + # define DP_PCON_FRL_BW_MASK_40GBPS (1 << 4) + # define DP_PCON_FRL_BW_MASK_48GBPS (1 << 5) + # define DP_PCON_FRL_LINK_TRAIN_EXTENDED (1 << 6) + # define DP_PCON_FRL_LINK_TRAIN_NORMAL (0 << 6) + + /* PCON HDMI LINK STATUS */ + #define DP_PCON_HDMI_TX_LINK_STATUS 0x303B + # define DP_PCON_HDMI_TX_LINK_ACTIVE (1 << 0) + # define DP_PCON_FRL_READY (1 << 1) + + /* PCON HDMI POST FRL STATUS */ + #define DP_PCON_HDMI_POST_FRL_STATUS 0x3036 + # define DP_PCON_HDMI_LINK_MODE (1 << 0) + # define DP_PCON_HDMI_MODE_TMDS 0 + # define DP_PCON_HDMI_MODE_FRL 1 + # define DP_PCON_HDMI_FRL_TRAINED_BW (0x3F << 1) + # define DP_PCON_FRL_TRAINED_BW_9GBPS (1 << 1) + # define DP_PCON_FRL_TRAINED_BW_18GBPS (1 << 2) + # define DP_PCON_FRL_TRAINED_BW_24GBPS (1 << 3) + # define DP_PCON_FRL_TRAINED_BW_32GBPS (1 << 4) + # define DP_PCON_FRL_TRAINED_BW_40GBPS (1 << 5) + # define DP_PCON_FRL_TRAINED_BW_48GBPS (1 << 6) + + #define DP_PROTOCOL_CONVERTER_CONTROL_0 0x3050 /* DP 1.3 */ + # define DP_HDMI_DVI_OUTPUT_CONFIG (1 << 0) /* DP 1.3 */ + #define DP_PROTOCOL_CONVERTER_CONTROL_1 0x3051 /* DP 1.3 */ + # define DP_CONVERSION_TO_YCBCR420_ENABLE (1 << 0) /* DP 1.3 */ + # define DP_HDMI_EDID_PROCESSING_DISABLE (1 << 1) /* DP 1.4 */ + # define DP_HDMI_AUTONOMOUS_SCRAMBLING_DISABLE (1 << 2) /* DP 1.4 */ + # define DP_HDMI_FORCE_SCRAMBLING (1 << 3) /* DP 1.4 */ + #define DP_PROTOCOL_CONVERTER_CONTROL_2 0x3052 /* DP 1.3 */ + # define DP_CONVERSION_TO_YCBCR422_ENABLE (1 << 0) /* DP 1.3 */ + # define DP_PCON_ENABLE_DSC_ENCODER (1 << 1) + # define DP_PCON_ENCODER_PPS_OVERRIDE_MASK (0x3 << 2) + # define DP_PCON_ENC_PPS_OVERRIDE_DISABLED 0 + # define DP_PCON_ENC_PPS_OVERRIDE_EN_PARAMS 1 + # define DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER 2 + # define DP_CONVERSION_RGB_YCBCR_MASK (7 << 4) + # define DP_CONVERSION_BT601_RGB_YCBCR_ENABLE (1 << 4) + # define DP_CONVERSION_BT709_RGB_YCBCR_ENABLE (1 << 5) + # define DP_CONVERSION_BT2020_RGB_YCBCR_ENABLE (1 << 6) + + /* PCON Downstream HDMI ERROR Status per Lane */ + #define DP_PCON_HDMI_ERROR_STATUS_LN0 0x3037 + #define DP_PCON_HDMI_ERROR_STATUS_LN1 0x3038 + #define DP_PCON_HDMI_ERROR_STATUS_LN2 0x3039 + #define DP_PCON_HDMI_ERROR_STATUS_LN3 0x303A + # define DP_PCON_HDMI_ERROR_COUNT_MASK (0x7 << 0) + # define DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS (1 << 0) + # define DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS (1 << 1) + # define DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS (1 << 2) + + /* PCON HDMI CONFIG PPS Override Buffer + * Valid Offsets to be added to Base : 0-127 + */ + #define DP_PCON_HDMI_PPS_OVERRIDE_BASE 0x3100 + + /* PCON HDMI CONFIG PPS Override Parameter: Slice height + * Offset-0 8LSBs of the Slice height. + * Offset-1 8MSBs of the Slice height. + */ + #define DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT 0x3180 + + /* PCON HDMI CONFIG PPS Override Parameter: Slice width + * Offset-0 8LSBs of the Slice width. + * Offset-1 8MSBs of the Slice width. + */ + #define DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH 0x3182 + + /* PCON HDMI CONFIG PPS Override Parameter: bits_per_pixel + * Offset-0 8LSBs of the bits_per_pixel. + * Offset-1 2MSBs of the bits_per_pixel. + */ + #define DP_PCON_HDMI_PPS_OVRD_BPP 0x3184 + + /* HDCP 1.3 and HDCP 2.2 */ + #define DP_AUX_HDCP_BKSV 0x68000 + #define DP_AUX_HDCP_RI_PRIME 0x68005 + #define DP_AUX_HDCP_AKSV 0x68007 + #define DP_AUX_HDCP_AN 0x6800C + #define DP_AUX_HDCP_V_PRIME(h) (0x68014 + h * 4) + #define DP_AUX_HDCP_BCAPS 0x68028 + # define DP_BCAPS_REPEATER_PRESENT BIT(1) + # define DP_BCAPS_HDCP_CAPABLE BIT(0) + #define DP_AUX_HDCP_BSTATUS 0x68029 + # define DP_BSTATUS_REAUTH_REQ BIT(3) + # define DP_BSTATUS_LINK_FAILURE BIT(2) + # define DP_BSTATUS_R0_PRIME_READY BIT(1) + # define DP_BSTATUS_READY BIT(0) + #define DP_AUX_HDCP_BINFO 0x6802A + #define DP_AUX_HDCP_KSV_FIFO 0x6802C + #define DP_AUX_HDCP_AINFO 0x6803B + + /* DP HDCP2.2 parameter offsets in DPCD address space */ + #define DP_HDCP_2_2_REG_RTX_OFFSET 0x69000 + #define DP_HDCP_2_2_REG_TXCAPS_OFFSET 0x69008 + #define DP_HDCP_2_2_REG_CERT_RX_OFFSET 0x6900B + #define DP_HDCP_2_2_REG_RRX_OFFSET 0x69215 + #define DP_HDCP_2_2_REG_RX_CAPS_OFFSET 0x6921D + #define DP_HDCP_2_2_REG_EKPUB_KM_OFFSET 0x69220 + #define DP_HDCP_2_2_REG_EKH_KM_WR_OFFSET 0x692A0 + #define DP_HDCP_2_2_REG_M_OFFSET 0x692B0 + #define DP_HDCP_2_2_REG_HPRIME_OFFSET 0x692C0 + #define DP_HDCP_2_2_REG_EKH_KM_RD_OFFSET 0x692E0 + #define DP_HDCP_2_2_REG_RN_OFFSET 0x692F0 + #define DP_HDCP_2_2_REG_LPRIME_OFFSET 0x692F8 + #define DP_HDCP_2_2_REG_EDKEY_KS_OFFSET 0x69318 + #define DP_HDCP_2_2_REG_RIV_OFFSET 0x69328 + #define DP_HDCP_2_2_REG_RXINFO_OFFSET 0x69330 + #define DP_HDCP_2_2_REG_SEQ_NUM_V_OFFSET 0x69332 + #define DP_HDCP_2_2_REG_VPRIME_OFFSET 0x69335 + #define DP_HDCP_2_2_REG_RECV_ID_LIST_OFFSET 0x69345 + #define DP_HDCP_2_2_REG_V_OFFSET 0x693E0 + #define DP_HDCP_2_2_REG_SEQ_NUM_M_OFFSET 0x693F0 + #define DP_HDCP_2_2_REG_K_OFFSET 0x693F3 + #define DP_HDCP_2_2_REG_STREAM_ID_TYPE_OFFSET 0x693F5 + #define DP_HDCP_2_2_REG_MPRIME_OFFSET 0x69473 + #define DP_HDCP_2_2_REG_RXSTATUS_OFFSET 0x69493 + #define DP_HDCP_2_2_REG_STREAM_TYPE_OFFSET 0x69494 + #define DP_HDCP_2_2_REG_DBG_OFFSET 0x69518 + + /* LTTPR: Link Training (LT)-tunable PHY Repeaters */ + #define DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV 0xf0000 /* 1.3 */ + #define DP_MAX_LINK_RATE_PHY_REPEATER 0xf0001 /* 1.4a */ + #define DP_PHY_REPEATER_CNT 0xf0002 /* 1.3 */ + #define DP_PHY_REPEATER_MODE 0xf0003 /* 1.3 */ + #define DP_MAX_LANE_COUNT_PHY_REPEATER 0xf0004 /* 1.4a */ + #define DP_Repeater_FEC_CAPABILITY 0xf0004 /* 1.4 */ + #define DP_PHY_REPEATER_EXTENDED_WAIT_TIMEOUT 0xf0005 /* 1.4a */ + #define DP_MAIN_LINK_CHANNEL_CODING_PHY_REPEATER 0xf0006 /* 2.0 */ + # define DP_PHY_REPEATER_128B132B_SUPPORTED (1 << 0) + /* See DP_128B132B_SUPPORTED_LINK_RATES for values */ + #define DP_PHY_REPEATER_128B132B_RATES 0xf0007 /* 2.0 */ + + enum drm_dp_phy { + DP_PHY_DPRX, + + DP_PHY_LTTPR1, + DP_PHY_LTTPR2, + DP_PHY_LTTPR3, + DP_PHY_LTTPR4, + DP_PHY_LTTPR5, + DP_PHY_LTTPR6, + DP_PHY_LTTPR7, + DP_PHY_LTTPR8, + + DP_MAX_LTTPR_COUNT = DP_PHY_LTTPR8, + }; + + #define DP_PHY_LTTPR(i) (DP_PHY_LTTPR1 + (i)) + + #define __DP_LTTPR1_BASE 0xf0010 /* 1.3 */ + #define __DP_LTTPR2_BASE 0xf0060 /* 1.3 */ + #define DP_LTTPR_BASE(dp_phy) \ + (__DP_LTTPR1_BASE + (__DP_LTTPR2_BASE - __DP_LTTPR1_BASE) * \ + ((dp_phy) - DP_PHY_LTTPR1)) + + #define DP_LTTPR_REG(dp_phy, lttpr1_reg) \ + (DP_LTTPR_BASE(dp_phy) - DP_LTTPR_BASE(DP_PHY_LTTPR1) + (lttpr1_reg)) + + #define DP_TRAINING_PATTERN_SET_PHY_REPEATER1 0xf0010 /* 1.3 */ + #define DP_TRAINING_PATTERN_SET_PHY_REPEATER(dp_phy) \ + DP_LTTPR_REG(dp_phy, DP_TRAINING_PATTERN_SET_PHY_REPEATER1) + + #define DP_TRAINING_LANE0_SET_PHY_REPEATER1 0xf0011 /* 1.3 */ + #define DP_TRAINING_LANE0_SET_PHY_REPEATER(dp_phy) \ + DP_LTTPR_REG(dp_phy, DP_TRAINING_LANE0_SET_PHY_REPEATER1) + + #define DP_TRAINING_LANE1_SET_PHY_REPEATER1 0xf0012 /* 1.3 */ + #define DP_TRAINING_LANE2_SET_PHY_REPEATER1 0xf0013 /* 1.3 */ + #define DP_TRAINING_LANE3_SET_PHY_REPEATER1 0xf0014 /* 1.3 */ + #define DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1 0xf0020 /* 1.4a */ + #define DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy) \ + DP_LTTPR_REG(dp_phy, DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) + + #define DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1 0xf0021 /* 1.4a */ + # define DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED BIT(0) + # define DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED BIT(1) + + #define DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1 0xf0022 /* 2.0 */ + #define DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy) \ + DP_LTTPR_REG(dp_phy, DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) + /* see DP_128B132B_TRAINING_AUX_RD_INTERVAL for values */ + + #define DP_LANE0_1_STATUS_PHY_REPEATER1 0xf0030 /* 1.3 */ + #define DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy) \ + DP_LTTPR_REG(dp_phy, DP_LANE0_1_STATUS_PHY_REPEATER1) + + #define DP_LANE2_3_STATUS_PHY_REPEATER1 0xf0031 /* 1.3 */ + + #define DP_LANE_ALIGN_STATUS_UPDATED_PHY_REPEATER1 0xf0032 /* 1.3 */ + #define DP_ADJUST_REQUEST_LANE0_1_PHY_REPEATER1 0xf0033 /* 1.3 */ + #define DP_ADJUST_REQUEST_LANE2_3_PHY_REPEATER1 0xf0034 /* 1.3 */ + #define DP_SYMBOL_ERROR_COUNT_LANE0_PHY_REPEATER1 0xf0035 /* 1.3 */ + #define DP_SYMBOL_ERROR_COUNT_LANE1_PHY_REPEATER1 0xf0037 /* 1.3 */ + #define DP_SYMBOL_ERROR_COUNT_LANE2_PHY_REPEATER1 0xf0039 /* 1.3 */ + #define DP_SYMBOL_ERROR_COUNT_LANE3_PHY_REPEATER1 0xf003b /* 1.3 */ + + #define __DP_FEC1_BASE 0xf0290 /* 1.4 */ + #define __DP_FEC2_BASE 0xf0298 /* 1.4 */ + #define DP_FEC_BASE(dp_phy) \ + (__DP_FEC1_BASE + ((__DP_FEC2_BASE - __DP_FEC1_BASE) * \ + ((dp_phy) - DP_PHY_LTTPR1))) + + #define DP_FEC_REG(dp_phy, fec1_reg) \ + (DP_FEC_BASE(dp_phy) - DP_FEC_BASE(DP_PHY_LTTPR1) + fec1_reg) + + #define DP_FEC_STATUS_PHY_REPEATER1 0xf0290 /* 1.4 */ + #define DP_FEC_STATUS_PHY_REPEATER(dp_phy) \ + DP_FEC_REG(dp_phy, DP_FEC_STATUS_PHY_REPEATER1) + + #define DP_FEC_ERROR_COUNT_PHY_REPEATER1 0xf0291 /* 1.4 */ + #define DP_FEC_CAPABILITY_PHY_REPEATER1 0xf0294 /* 1.4a */ + + #define DP_LTTPR_MAX_ADD 0xf02ff /* 1.4 */ + + #define DP_DPCD_MAX_ADD 0xfffff /* 1.4 */ + + /* Repeater modes */ + #define DP_PHY_REPEATER_MODE_TRANSPARENT 0x55 /* 1.3 */ + #define DP_PHY_REPEATER_MODE_NON_TRANSPARENT 0xaa /* 1.3 */ + + /* DP HDCP message start offsets in DPCD address space */ + #define DP_HDCP_2_2_AKE_INIT_OFFSET DP_HDCP_2_2_REG_RTX_OFFSET + #define DP_HDCP_2_2_AKE_SEND_CERT_OFFSET DP_HDCP_2_2_REG_CERT_RX_OFFSET + #define DP_HDCP_2_2_AKE_NO_STORED_KM_OFFSET DP_HDCP_2_2_REG_EKPUB_KM_OFFSET + #define DP_HDCP_2_2_AKE_STORED_KM_OFFSET DP_HDCP_2_2_REG_EKH_KM_WR_OFFSET + #define DP_HDCP_2_2_AKE_SEND_HPRIME_OFFSET DP_HDCP_2_2_REG_HPRIME_OFFSET + #define DP_HDCP_2_2_AKE_SEND_PAIRING_INFO_OFFSET \ + DP_HDCP_2_2_REG_EKH_KM_RD_OFFSET + #define DP_HDCP_2_2_LC_INIT_OFFSET DP_HDCP_2_2_REG_RN_OFFSET + #define DP_HDCP_2_2_LC_SEND_LPRIME_OFFSET DP_HDCP_2_2_REG_LPRIME_OFFSET + #define DP_HDCP_2_2_SKE_SEND_EKS_OFFSET DP_HDCP_2_2_REG_EDKEY_KS_OFFSET + #define DP_HDCP_2_2_REP_SEND_RECVID_LIST_OFFSET DP_HDCP_2_2_REG_RXINFO_OFFSET + #define DP_HDCP_2_2_REP_SEND_ACK_OFFSET DP_HDCP_2_2_REG_V_OFFSET + #define DP_HDCP_2_2_REP_STREAM_MANAGE_OFFSET DP_HDCP_2_2_REG_SEQ_NUM_M_OFFSET + #define DP_HDCP_2_2_REP_STREAM_READY_OFFSET DP_HDCP_2_2_REG_MPRIME_OFFSET + + #define HDCP_2_2_DP_RXSTATUS_LEN 1 + #define HDCP_2_2_DP_RXSTATUS_READY(x) ((x) & BIT(0)) + #define HDCP_2_2_DP_RXSTATUS_H_PRIME(x) ((x) & BIT(1)) + #define HDCP_2_2_DP_RXSTATUS_PAIRING(x) ((x) & BIT(2)) + #define HDCP_2_2_DP_RXSTATUS_REAUTH_REQ(x) ((x) & BIT(3)) + #define HDCP_2_2_DP_RXSTATUS_LINK_FAILED(x) ((x) & BIT(4)) + + /* DP 1.2 Sideband message defines */ + /* peer device type - DP 1.2a Table 2-92 */ + #define DP_PEER_DEVICE_NONE 0x0 + #define DP_PEER_DEVICE_SOURCE_OR_SST 0x1 + #define DP_PEER_DEVICE_MST_BRANCHING 0x2 + #define DP_PEER_DEVICE_SST_SINK 0x3 + #define DP_PEER_DEVICE_DP_LEGACY_CONV 0x4 + + /* DP 1.2 MST sideband request names DP 1.2a Table 2-80 */ + #define DP_GET_MSG_TRANSACTION_VERSION 0x00 /* DP 1.3 */ + #define DP_LINK_ADDRESS 0x01 + #define DP_CONNECTION_STATUS_NOTIFY 0x02 + #define DP_ENUM_PATH_RESOURCES 0x10 + #define DP_ALLOCATE_PAYLOAD 0x11 + #define DP_QUERY_PAYLOAD 0x12 + #define DP_RESOURCE_STATUS_NOTIFY 0x13 + #define DP_CLEAR_PAYLOAD_ID_TABLE 0x14 + #define DP_REMOTE_DPCD_READ 0x20 + #define DP_REMOTE_DPCD_WRITE 0x21 + #define DP_REMOTE_I2C_READ 0x22 + #define DP_REMOTE_I2C_WRITE 0x23 + #define DP_POWER_UP_PHY 0x24 + #define DP_POWER_DOWN_PHY 0x25 + #define DP_SINK_EVENT_NOTIFY 0x30 + #define DP_QUERY_STREAM_ENC_STATUS 0x38 + #define DP_QUERY_STREAM_ENC_STATUS_STATE_NO_EXIST 0 + #define DP_QUERY_STREAM_ENC_STATUS_STATE_INACTIVE 1 + #define DP_QUERY_STREAM_ENC_STATUS_STATE_ACTIVE 2 + + /* DP 1.2 MST sideband reply types */ + #define DP_SIDEBAND_REPLY_ACK 0x00 + #define DP_SIDEBAND_REPLY_NAK 0x01 + + /* DP 1.2 MST sideband nak reasons - table 2.84 */ + #define DP_NAK_WRITE_FAILURE 0x01 + #define DP_NAK_INVALID_READ 0x02 + #define DP_NAK_CRC_FAILURE 0x03 + #define DP_NAK_BAD_PARAM 0x04 + #define DP_NAK_DEFER 0x05 + #define DP_NAK_LINK_FAILURE 0x06 + #define DP_NAK_NO_RESOURCES 0x07 + #define DP_NAK_DPCD_FAIL 0x08 + #define DP_NAK_I2C_NAK 0x09 + #define DP_NAK_ALLOCATE_FAIL 0x0a + + #define MODE_I2C_START 1 + #define MODE_I2C_WRITE 2 + #define MODE_I2C_READ 4 + #define MODE_I2C_STOP 8 + + /* DP 1.2 MST PORTs - Section 2.5.1 v1.2a spec */ + #define DP_MST_PHYSICAL_PORT_0 0 + #define DP_MST_LOGICAL_PORT_0 8 + + #define DP_LINK_CONSTANT_N_VALUE 0x8000 + #define DP_LINK_STATUS_SIZE 6 + bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count); + bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count); + u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane); + u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane); + u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane); + u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZE], + unsigned int lane); + + #define DP_BRANCH_OUI_HEADER_SIZE 0xc + #define DP_RECEIVER_CAP_SIZE 0xf + #define DP_DSC_RECEIVER_CAP_SIZE 0xf + #define EDP_PSR_RECEIVER_CAP_SIZE 2 + #define EDP_DISPLAY_CTL_CAP_SIZE 3 + #define DP_LTTPR_COMMON_CAP_SIZE 8 + #define DP_LTTPR_PHY_CAP_SIZE 3 + + int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr); + int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr); + + void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]); + void drm_dp_lttpr_link_train_clock_recovery_delay(void); + void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]); + void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 caps[DP_LTTPR_PHY_CAP_SIZE]); + + u8 drm_dp_link_rate_to_bw_code(int link_rate); + int drm_dp_bw_code_to_link_rate(u8 link_bw); + + #define DP_SDP_AUDIO_TIMESTAMP 0x01 + #define DP_SDP_AUDIO_STREAM 0x02 + #define DP_SDP_EXTENSION 0x04 /* DP 1.1 */ + #define DP_SDP_AUDIO_COPYMANAGEMENT 0x05 /* DP 1.2 */ + #define DP_SDP_ISRC 0x06 /* DP 1.2 */ + #define DP_SDP_VSC 0x07 /* DP 1.2 */ + #define DP_SDP_CAMERA_GENERIC(i) (0x08 + (i)) /* 0-7, DP 1.3 */ + #define DP_SDP_PPS 0x10 /* DP 1.4 */ + #define DP_SDP_VSC_EXT_VESA 0x20 /* DP 1.4 */ + #define DP_SDP_VSC_EXT_CEA 0x21 /* DP 1.4 */ + /* 0x80+ CEA-861 infoframe types */ + + /** + * struct dp_sdp_header - DP secondary data packet header + * @HB0: Secondary Data Packet ID + * @HB1: Secondary Data Packet Type + * @HB2: Secondary Data Packet Specific header, Byte 0 + * @HB3: Secondary Data packet Specific header, Byte 1 + */ + struct dp_sdp_header { + u8 HB0; + u8 HB1; + u8 HB2; + u8 HB3; + } __packed; + + #define EDP_SDP_HEADER_REVISION_MASK 0x1F + #define EDP_SDP_HEADER_VALID_PAYLOAD_BYTES 0x1F + #define DP_SDP_PPS_HEADER_PAYLOAD_BYTES_MINUS_1 0x7F + + /** + * struct dp_sdp - DP secondary data packet + * @sdp_header: DP secondary data packet header + * @db: DP secondaray data packet data blocks + * VSC SDP Payload for PSR + * db[0]: Stereo Interface + * db[1]: 0 - PSR State; 1 - Update RFB; 2 - CRC Valid + * db[2]: CRC value bits 7:0 of the R or Cr component + * db[3]: CRC value bits 15:8 of the R or Cr component + * db[4]: CRC value bits 7:0 of the G or Y component + * db[5]: CRC value bits 15:8 of the G or Y component + * db[6]: CRC value bits 7:0 of the B or Cb component + * db[7]: CRC value bits 15:8 of the B or Cb component + * db[8] - db[31]: Reserved + * VSC SDP Payload for Pixel Encoding/Colorimetry Format + * db[0] - db[15]: Reserved + * db[16]: Pixel Encoding and Colorimetry Formats + * db[17]: Dynamic Range and Component Bit Depth + * db[18]: Content Type + * db[19] - db[31]: Reserved + */ + struct dp_sdp { + struct dp_sdp_header sdp_header; + u8 db[32]; + } __packed; + + #define EDP_VSC_PSR_STATE_ACTIVE (1<<0) + #define EDP_VSC_PSR_UPDATE_RFB (1<<1) + #define EDP_VSC_PSR_CRC_VALUES_VALID (1<<2) + + /** + * enum dp_pixelformat - drm DP Pixel encoding formats + * + * This enum is used to indicate DP VSC SDP Pixel encoding formats. + * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through + * DB18] + * + * @DP_PIXELFORMAT_RGB: RGB pixel encoding format + * @DP_PIXELFORMAT_YUV444: YCbCr 4:4:4 pixel encoding format + * @DP_PIXELFORMAT_YUV422: YCbCr 4:2:2 pixel encoding format + * @DP_PIXELFORMAT_YUV420: YCbCr 4:2:0 pixel encoding format + * @DP_PIXELFORMAT_Y_ONLY: Y Only pixel encoding format + * @DP_PIXELFORMAT_RAW: RAW pixel encoding format + * @DP_PIXELFORMAT_RESERVED: Reserved pixel encoding format + */ + enum dp_pixelformat { + DP_PIXELFORMAT_RGB = 0, + DP_PIXELFORMAT_YUV444 = 0x1, + DP_PIXELFORMAT_YUV422 = 0x2, + DP_PIXELFORMAT_YUV420 = 0x3, + DP_PIXELFORMAT_Y_ONLY = 0x4, + DP_PIXELFORMAT_RAW = 0x5, + DP_PIXELFORMAT_RESERVED = 0x6, + }; + + /** + * enum dp_colorimetry - drm DP Colorimetry formats + * + * This enum is used to indicate DP VSC SDP Colorimetry formats. + * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through + * DB18] and a name of enum member follows DRM_MODE_COLORIMETRY definition. + * + * @DP_COLORIMETRY_DEFAULT: sRGB (IEC 61966-2-1) or + * ITU-R BT.601 colorimetry format + * @DP_COLORIMETRY_RGB_WIDE_FIXED: RGB wide gamut fixed point colorimetry format + * @DP_COLORIMETRY_BT709_YCC: ITU-R BT.709 colorimetry format + * @DP_COLORIMETRY_RGB_WIDE_FLOAT: RGB wide gamut floating point + * (scRGB (IEC 61966-2-2)) colorimetry format + * @DP_COLORIMETRY_XVYCC_601: xvYCC601 colorimetry format + * @DP_COLORIMETRY_OPRGB: OpRGB colorimetry format + * @DP_COLORIMETRY_XVYCC_709: xvYCC709 colorimetry format + * @DP_COLORIMETRY_DCI_P3_RGB: DCI-P3 (SMPTE RP 431-2) colorimetry format + * @DP_COLORIMETRY_SYCC_601: sYCC601 colorimetry format + * @DP_COLORIMETRY_RGB_CUSTOM: RGB Custom Color Profile colorimetry format + * @DP_COLORIMETRY_OPYCC_601: opYCC601 colorimetry format + * @DP_COLORIMETRY_BT2020_RGB: ITU-R BT.2020 R' G' B' colorimetry format + * @DP_COLORIMETRY_BT2020_CYCC: ITU-R BT.2020 Y'c C'bc C'rc colorimetry format + * @DP_COLORIMETRY_BT2020_YCC: ITU-R BT.2020 Y' C'b C'r colorimetry format + */ + enum dp_colorimetry { + DP_COLORIMETRY_DEFAULT = 0, + DP_COLORIMETRY_RGB_WIDE_FIXED = 0x1, + DP_COLORIMETRY_BT709_YCC = 0x1, + DP_COLORIMETRY_RGB_WIDE_FLOAT = 0x2, + DP_COLORIMETRY_XVYCC_601 = 0x2, + DP_COLORIMETRY_OPRGB = 0x3, + DP_COLORIMETRY_XVYCC_709 = 0x3, + DP_COLORIMETRY_DCI_P3_RGB = 0x4, + DP_COLORIMETRY_SYCC_601 = 0x4, + DP_COLORIMETRY_RGB_CUSTOM = 0x5, + DP_COLORIMETRY_OPYCC_601 = 0x5, + DP_COLORIMETRY_BT2020_RGB = 0x6, + DP_COLORIMETRY_BT2020_CYCC = 0x6, + DP_COLORIMETRY_BT2020_YCC = 0x7, + }; + + /** + * enum dp_dynamic_range - drm DP Dynamic Range + * + * This enum is used to indicate DP VSC SDP Dynamic Range. + * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through + * DB18] + * + * @DP_DYNAMIC_RANGE_VESA: VESA range + * @DP_DYNAMIC_RANGE_CTA: CTA range + */ + enum dp_dynamic_range { + DP_DYNAMIC_RANGE_VESA = 0, + DP_DYNAMIC_RANGE_CTA = 1, + }; + + /** + * enum dp_content_type - drm DP Content Type + * + * This enum is used to indicate DP VSC SDP Content Types. + * It is based on DP 1.4 spec [Table 2-117: VSC SDP Payload for DB16 through + * DB18] + * CTA-861-G defines content types and expected processing by a sink device + * + * @DP_CONTENT_TYPE_NOT_DEFINED: Not defined type + * @DP_CONTENT_TYPE_GRAPHICS: Graphics type + * @DP_CONTENT_TYPE_PHOTO: Photo type + * @DP_CONTENT_TYPE_VIDEO: Video type + * @DP_CONTENT_TYPE_GAME: Game type + */ + enum dp_content_type { + DP_CONTENT_TYPE_NOT_DEFINED = 0x00, + DP_CONTENT_TYPE_GRAPHICS = 0x01, + DP_CONTENT_TYPE_PHOTO = 0x02, + DP_CONTENT_TYPE_VIDEO = 0x03, + DP_CONTENT_TYPE_GAME = 0x04, + }; + + /** + * struct drm_dp_vsc_sdp - drm DP VSC SDP + * + * This structure represents a DP VSC SDP of drm + * It is based on DP 1.4 spec [Table 2-116: VSC SDP Header Bytes] and + * [Table 2-117: VSC SDP Payload for DB16 through DB18] + * + * @sdp_type: secondary-data packet type + * @revision: revision number + * @length: number of valid data bytes + * @pixelformat: pixel encoding format + * @colorimetry: colorimetry format + * @bpc: bit per color + * @dynamic_range: dynamic range information + * @content_type: CTA-861-G defines content types and expected processing by a sink device + */ + struct drm_dp_vsc_sdp { + unsigned char sdp_type; + unsigned char revision; + unsigned char length; + enum dp_pixelformat pixelformat; + enum dp_colorimetry colorimetry; + int bpc; + enum dp_dynamic_range dynamic_range; + enum dp_content_type content_type; + }; + + void drm_dp_vsc_sdp_log(const char *level, struct device *dev, + const struct drm_dp_vsc_sdp *vsc); + + int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]); + + static inline int + drm_dp_max_link_rate(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return drm_dp_bw_code_to_link_rate(dpcd[DP_MAX_LINK_RATE]); + } + + static inline u8 + drm_dp_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK; + } + + static inline bool + drm_dp_enhanced_frame_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DPCD_REV] >= 0x11 && + (dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP); + } + + static inline bool + drm_dp_fast_training_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DPCD_REV] >= 0x11 && + (dpcd[DP_MAX_DOWNSPREAD] & DP_NO_AUX_HANDSHAKE_LINK_TRAINING); + } + + static inline bool + drm_dp_tps3_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DPCD_REV] >= 0x12 && + dpcd[DP_MAX_LANE_COUNT] & DP_TPS3_SUPPORTED; + } + ++static inline bool ++drm_dp_max_downspread(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) ++{ ++ return dpcd[DP_DPCD_REV] >= 0x11 || ++ dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5; ++} ++ + static inline bool + drm_dp_tps4_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DPCD_REV] >= 0x14 && + dpcd[DP_MAX_DOWNSPREAD] & DP_TPS4_SUPPORTED; + } + + static inline u8 + drm_dp_training_pattern_mask(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return (dpcd[DP_DPCD_REV] >= 0x14) ? DP_TRAINING_PATTERN_MASK_1_4 : + DP_TRAINING_PATTERN_MASK; + } + + static inline bool + drm_dp_is_branch(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT; + } + + /* DP/eDP DSC support */ + u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], + bool is_edp); + u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]); + int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpc[DP_DSC_RECEIVER_CAP_SIZE], + u8 dsc_bpc[3]); + + static inline bool + drm_dp_sink_supports_dsc(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) + { + return dsc_dpcd[DP_DSC_SUPPORT - DP_DSC_SUPPORT] & + DP_DSC_DECOMPRESSION_IS_SUPPORTED; + } + + static inline u16 + drm_edp_dsc_sink_output_bpp(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) + { + return dsc_dpcd[DP_DSC_MAX_BITS_PER_PIXEL_LOW - DP_DSC_SUPPORT] | + (dsc_dpcd[DP_DSC_MAX_BITS_PER_PIXEL_HI - DP_DSC_SUPPORT] & + DP_DSC_MAX_BITS_PER_PIXEL_HI_MASK << + DP_DSC_MAX_BITS_PER_PIXEL_HI_SHIFT); + } + + static inline u32 + drm_dp_dsc_sink_max_slice_width(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) + { + /* Max Slicewidth = Number of Pixels * 320 */ + return dsc_dpcd[DP_DSC_MAX_SLICE_WIDTH - DP_DSC_SUPPORT] * + DP_DSC_SLICE_WIDTH_MULTIPLIER; + } + + /* Forward Error Correction Support on DP 1.4 */ + static inline bool + drm_dp_sink_supports_fec(const u8 fec_capable) + { + return fec_capable & DP_FEC_CAPABLE; + } + + static inline bool + drm_dp_channel_coding_supported(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_MAIN_LINK_CHANNEL_CODING] & DP_CAP_ANSI_8B10B; + } + + static inline bool + drm_dp_alternate_scrambler_reset_cap(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_EDP_CONFIGURATION_CAP] & + DP_ALTERNATE_SCRAMBLER_RESET_CAP; + } + + /* Ignore MSA timing for Adaptive Sync support on DP 1.4 */ + static inline bool + drm_dp_sink_can_do_video_without_timing_msa(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) + { + return dpcd[DP_DOWN_STREAM_PORT_COUNT] & + DP_MSA_TIMING_PAR_IGNORED; + } + + /** + * drm_edp_backlight_supported() - Check an eDP DPCD for VESA backlight support + * @edp_dpcd: The DPCD to check + * + * Note that currently this function will return %false for panels which support various DPCD + * backlight features but which require the brightness be set through PWM, and don't support setting + * the brightness level via the DPCD. + * + * Returns: %True if @edp_dpcd indicates that VESA backlight controls are supported, %false + * otherwise + */ + static inline bool + drm_edp_backlight_supported(const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) + { + return !!(edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP); + } + + /* + * DisplayPort AUX channel + */ + + /** + * struct drm_dp_aux_msg - DisplayPort AUX channel transaction + * @address: address of the (first) register to access + * @request: contains the type of transaction (see DP_AUX_* macros) + * @reply: upon completion, contains the reply type of the transaction + * @buffer: pointer to a transmission or reception buffer + * @size: size of @buffer + */ + struct drm_dp_aux_msg { + unsigned int address; + u8 request; + u8 reply; + void *buffer; + size_t size; + }; + + struct cec_adapter; + struct edid; + struct drm_connector; + + /** + * struct drm_dp_aux_cec - DisplayPort CEC-Tunneling-over-AUX + * @lock: mutex protecting this struct + * @adap: the CEC adapter for CEC-Tunneling-over-AUX support. + * @connector: the connector this CEC adapter is associated with + * @unregister_work: unregister the CEC adapter + */ + struct drm_dp_aux_cec { + struct mutex lock; + struct cec_adapter *adap; + struct drm_connector *connector; + struct delayed_work unregister_work; + }; + + /** + * struct drm_dp_aux - DisplayPort AUX channel + * + * An AUX channel can also be used to transport I2C messages to a sink. A + * typical application of that is to access an EDID that's present in the sink + * device. The @transfer() function can also be used to execute such + * transactions. The drm_dp_aux_register() function registers an I2C adapter + * that can be passed to drm_probe_ddc(). Upon removal, drivers should call + * drm_dp_aux_unregister() to remove the I2C adapter. The I2C adapter uses long + * transfers by default; if a partial response is received, the adapter will + * drop down to the size given by the partial response for this transaction + * only. + */ + struct drm_dp_aux { + /** + * @name: user-visible name of this AUX channel and the + * I2C-over-AUX adapter. + * + * It's also used to specify the name of the I2C adapter. If set + * to %NULL, dev_name() of @dev will be used. + */ + const char *name; + + /** + * @ddc: I2C adapter that can be used for I2C-over-AUX + * communication + */ + struct i2c_adapter ddc; + + /** + * @dev: pointer to struct device that is the parent for this + * AUX channel. + */ + struct device *dev; + + /** + * @drm_dev: pointer to the &drm_device that owns this AUX channel. + * Beware, this may be %NULL before drm_dp_aux_register() has been + * called. + * + * It should be set to the &drm_device that will be using this AUX + * channel as early as possible. For many graphics drivers this should + * happen before drm_dp_aux_init(), however it's perfectly fine to set + * this field later so long as it's assigned before calling + * drm_dp_aux_register(). + */ + struct drm_device *drm_dev; + + /** + * @crtc: backpointer to the crtc that is currently using this + * AUX channel + */ + struct drm_crtc *crtc; + + /** + * @hw_mutex: internal mutex used for locking transfers. + * + * Note that if the underlying hardware is shared among multiple + * channels, the driver needs to do additional locking to + * prevent concurrent access. + */ + struct mutex hw_mutex; + + /** + * @crc_work: worker that captures CRCs for each frame + */ + struct work_struct crc_work; + + /** + * @crc_count: counter of captured frame CRCs + */ + u8 crc_count; + + /** + * @transfer: transfers a message representing a single AUX + * transaction. + * + * This is a hardware-specific implementation of how + * transactions are executed that the drivers must provide. + * + * A pointer to a &drm_dp_aux_msg structure describing the + * transaction is passed into this function. Upon success, the + * implementation should return the number of payload bytes that + * were transferred, or a negative error-code on failure. + * + * Helpers will propagate these errors, with the exception of + * the %-EBUSY error, which causes a transaction to be retried. + * On a short, helpers will return %-EPROTO to make it simpler + * to check for failure. + * + * The @transfer() function must only modify the reply field of + * the &drm_dp_aux_msg structure. The retry logic and i2c + * helpers assume this is the case. + * + * Also note that this callback can be called no matter the + * state @dev is in. Drivers that need that device to be powered + * to perform this operation will first need to make sure it's + * been properly enabled. + */ + ssize_t (*transfer)(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg); + + /** + * @i2c_nack_count: Counts I2C NACKs, used for DP validation. + */ + unsigned i2c_nack_count; + /** + * @i2c_defer_count: Counts I2C DEFERs, used for DP validation. + */ + unsigned i2c_defer_count; + /** + * @cec: struct containing fields used for CEC-Tunneling-over-AUX. + */ + struct drm_dp_aux_cec cec; + /** + * @is_remote: Is this AUX CH actually using sideband messaging. + */ + bool is_remote; + }; + + ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size); + ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size); + + /** + * drm_dp_dpcd_readb() - read a single byte from the DPCD + * @aux: DisplayPort AUX channel + * @offset: address of the register to read + * @valuep: location where the value of the register will be stored + * + * Returns the number of bytes transferred (1) on success, or a negative + * error code on failure. + */ + static inline ssize_t drm_dp_dpcd_readb(struct drm_dp_aux *aux, + unsigned int offset, u8 *valuep) + { + return drm_dp_dpcd_read(aux, offset, valuep, 1); + } + + /** + * drm_dp_dpcd_writeb() - write a single byte to the DPCD + * @aux: DisplayPort AUX channel + * @offset: address of the register to write + * @value: value to write to the register + * + * Returns the number of bytes transferred (1) on success, or a negative + * error code on failure. + */ + static inline ssize_t drm_dp_dpcd_writeb(struct drm_dp_aux *aux, + unsigned int offset, u8 value) + { + return drm_dp_dpcd_write(aux, offset, &value, 1); + } + + int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux, + u8 dpcd[DP_RECEIVER_CAP_SIZE]); + + int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux, + u8 status[DP_LINK_STATUS_SIZE]); + + int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, + enum drm_dp_phy dp_phy, + u8 link_status[DP_LINK_STATUS_SIZE]); + + bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux, + u8 real_edid_checksum); + + int drm_dp_read_downstream_info(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]); + bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], u8 type); + bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid); + int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid); + int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid); + int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid); + bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + struct drm_display_mode *drm_dp_downstream_mode(struct drm_device *dev, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]); + void drm_dp_downstream_debug(struct seq_file *m, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid, + struct drm_dp_aux *aux); + enum drm_mode_subconnector + drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + void drm_dp_set_subconnector_property(struct drm_connector *connector, + enum drm_connector_status status, + const u8 *dpcd, + const u8 port_cap[4]); + + struct drm_dp_desc; + bool drm_dp_read_sink_count_cap(struct drm_connector *connector, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const struct drm_dp_desc *desc); + int drm_dp_read_sink_count(struct drm_dp_aux *aux); + + int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux, + u8 caps[DP_LTTPR_COMMON_CAP_SIZE]); + int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux, + enum drm_dp_phy dp_phy, + u8 caps[DP_LTTPR_PHY_CAP_SIZE]); + int drm_dp_lttpr_count(const u8 cap[DP_LTTPR_COMMON_CAP_SIZE]); + int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]); + int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]); + bool drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]); + bool drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]); + + void drm_dp_remote_aux_init(struct drm_dp_aux *aux); + void drm_dp_aux_init(struct drm_dp_aux *aux); + int drm_dp_aux_register(struct drm_dp_aux *aux); + void drm_dp_aux_unregister(struct drm_dp_aux *aux); + + int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc); + int drm_dp_stop_crc(struct drm_dp_aux *aux); + + struct drm_dp_dpcd_ident { + u8 oui[3]; + u8 device_id[6]; + u8 hw_rev; + u8 sw_major_rev; + u8 sw_minor_rev; + } __packed; + + /** + * struct drm_dp_desc - DP branch/sink device descriptor + * @ident: DP device identification from DPCD 0x400 (sink) or 0x500 (branch). + * @quirks: Quirks; use drm_dp_has_quirk() to query for the quirks. + */ + struct drm_dp_desc { + struct drm_dp_dpcd_ident ident; + u32 quirks; + }; + + int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, + bool is_branch); + + /** + * enum drm_dp_quirk - Display Port sink/branch device specific quirks + * + * Display Port sink and branch devices in the wild have a variety of bugs, try + * to collect them here. The quirks are shared, but it's up to the drivers to + * implement workarounds for them. + */ + enum drm_dp_quirk { + /** + * @DP_DPCD_QUIRK_CONSTANT_N: + * + * The device requires main link attributes Mvid and Nvid to be limited + * to 16 bits. So will give a constant value (0x8000) for compatability. + */ + DP_DPCD_QUIRK_CONSTANT_N, + /** + * @DP_DPCD_QUIRK_NO_PSR: + * + * The device does not support PSR even if reports that it supports or + * driver still need to implement proper handling for such device. + */ + DP_DPCD_QUIRK_NO_PSR, + /** + * @DP_DPCD_QUIRK_NO_SINK_COUNT: + * + * The device does not set SINK_COUNT to a non-zero value. + * The driver should ignore SINK_COUNT during detection. Note that + * drm_dp_read_sink_count_cap() automatically checks for this quirk. + */ + DP_DPCD_QUIRK_NO_SINK_COUNT, + /** + * @DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD: + * + * The device supports MST DSC despite not supporting Virtual DPCD. + * The DSC caps can be read from the physical aux instead. + */ + DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD, + /** + * @DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS: + * + * The device supports a link rate of 3.24 Gbps (multiplier 0xc) despite + * the DP_MAX_LINK_RATE register reporting a lower max multiplier. + */ + DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS, + }; + + /** + * drm_dp_has_quirk() - does the DP device have a specific quirk + * @desc: Device descriptor filled by drm_dp_read_desc() + * @quirk: Quirk to query for + * + * Return true if DP device identified by @desc has @quirk. + */ + static inline bool + drm_dp_has_quirk(const struct drm_dp_desc *desc, enum drm_dp_quirk quirk) + { + return desc->quirks & BIT(quirk); + } + + /** + * struct drm_edp_backlight_info - Probed eDP backlight info struct + * @pwmgen_bit_count: The pwmgen bit count + * @pwm_freq_pre_divider: The PWM frequency pre-divider value being used for this backlight, if any + * @max: The maximum backlight level that may be set + * @lsb_reg_used: Do we also write values to the DP_EDP_BACKLIGHT_BRIGHTNESS_LSB register? + * @aux_enable: Does the panel support the AUX enable cap? + * @aux_set: Does the panel support setting the brightness through AUX? + * + * This structure contains various data about an eDP backlight, which can be populated by using + * drm_edp_backlight_init(). + */ + struct drm_edp_backlight_info { + u8 pwmgen_bit_count; + u8 pwm_freq_pre_divider; + u16 max; + + bool lsb_reg_used : 1; + bool aux_enable : 1; + bool aux_set : 1; + }; + + int + drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], + u16 *current_level, u8 *current_mode); + int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + u16 level); + int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + u16 level); + int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl); + + #if IS_ENABLED(CONFIG_DRM_KMS_HELPER) && (IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ + (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE))) + + int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux); + + #else + + static inline int drm_panel_dp_aux_backlight(struct drm_panel *panel, + struct drm_dp_aux *aux) + { + return 0; + } + + #endif + + #ifdef CONFIG_DRM_DP_CEC + void drm_dp_cec_irq(struct drm_dp_aux *aux); + void drm_dp_cec_register_connector(struct drm_dp_aux *aux, + struct drm_connector *connector); + void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux); + void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid); + void drm_dp_cec_unset_edid(struct drm_dp_aux *aux); + #else + static inline void drm_dp_cec_irq(struct drm_dp_aux *aux) + { + } + + static inline void + drm_dp_cec_register_connector(struct drm_dp_aux *aux, + struct drm_connector *connector) + { + } + + static inline void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) + { + } + + static inline void drm_dp_cec_set_edid(struct drm_dp_aux *aux, + const struct edid *edid) + { + } + + static inline void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) + { + } + + #endif + + /** + * struct drm_dp_phy_test_params - DP Phy Compliance parameters + * @link_rate: Requested Link rate from DPCD 0x219 + * @num_lanes: Number of lanes requested by sing through DPCD 0x220 + * @phy_pattern: DP Phy test pattern from DPCD 0x248 + * @hbr2_reset: DP HBR2_COMPLIANCE_SCRAMBLER_RESET from DCPD 0x24A and 0x24B + * @custom80: DP Test_80BIT_CUSTOM_PATTERN from DPCDs 0x250 through 0x259 + * @enhanced_frame_cap: flag for enhanced frame capability. + */ + struct drm_dp_phy_test_params { + int link_rate; + u8 num_lanes; + u8 phy_pattern; + u8 hbr2_reset[2]; + u8 custom80[10]; + bool enhanced_frame_cap; + }; + + int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data); + int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data, u8 dp_rev); + int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]); + int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd); + bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux); + int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps, + u8 frl_mode); + int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask, + u8 frl_type); + int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux); + int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux); + + bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux); + int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask); + void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux, + struct drm_connector *connector); + bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]); + int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]); + int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]); + int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]); + int drm_dp_pcon_pps_default(struct drm_dp_aux *aux); + int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]); + int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]); + bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], u8 color_spc); + int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc); + + #endif /* _DRM_DP_HELPER_H_ */