--- /dev/null
+ /*
+ * 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 <linux/bitfield.h>
+ #include <linux/delay.h>
+ #include <linux/errno.h>
+ #include <linux/i2c.h>
+ #include <linux/init.h>
+ #include <linux/kernel.h>
+ #include <linux/random.h>
+ #include <linux/sched.h>
+ #include <linux/seq_file.h>
+ #include <linux/iopoll.h>
+
+ #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
+ #include <linux/stacktrace.h>
+ #include <linux/sort.h>
+ #include <linux/timekeeping.h>
+ #include <linux/math64.h>
+ #endif
+
+ #include <drm/dp/drm_dp_mst_helper.h>
+ #include <drm/drm_atomic.h>
+ #include <drm/drm_atomic_helper.h>
+ #include <drm/drm_drv.h>
+ #include <drm/drm_print.h>
+ #include <drm/drm_probe_helper.h>
+
+ #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, "<failed to decode sideband req: %d>\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);
--- /dev/null
+ /*
+ * 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 <linux/delay.h>
+ #include <linux/i2c.h>
+ #include <linux/types.h>
+ #include <drm/drm_connector.h>
+
+ 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_ */