/* Decode a sideband request we've encoded, mainly used for debugging */ int
drm_dp_decode_sideband_req(conststruct 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;
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;
}
}
/* 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) returnfalse;
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;
returntrue;
}
/* this adds a chunk of msg to the builder to get the final msg */ staticbool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg,
u8 *replybuf, u8 replybuflen)
{
u8 crc4;
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;
/* First, sort the list so that it goes from oldest to newest *referenceentry
*/
sort(history->entries, history->len, sizeof(*history->entries),
topology_ref_history_cmp, NULL);
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;
}
/* *calculateanewRADforthisMSTbranchdevice *ifparenthasanLCTof2thenithas1nibbleofRAD, *ifparenthasanLCTof3thenithas2nibblesofRAD,
*/ 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;
staticbool 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: returntrue; case DP_PEER_DEVICE_MST_BRANCHING: /* For sst branch device */ if (!mcs) returntrue;
if (port->pdt == new_pdt && port->mcs == new_mcs) return0;
/* 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)) { /* *IfthenewPDTwouldalsohaveani2cbus, *don'tbotherwithreregisteringit
*/ 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; return0;
}
/* 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);
}
/* *ReprobePBNcapsonbothhotplug,andwhenre-probingthelink *forourparentmstb
*/ 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;
} elseif (ret < 0) {
drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); goto fail;
}
/* Locking is only needed if the port's exposed to userspace */
drm_modeset_lock(&mgr->base.lock, NULL);
} elseif (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;
}
staticstruct 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 = 1; i < lct; i++) { int port_num = drm_dp_mst_get_ufp_num_at_lct_from_rad(i + 1, rad);
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;
}
staticint 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_send_sideband_msg(mgr, up, chunk, idx); if (ret) { if (drm_debug_enabled(DRM_UT_DP)) { struct drm_printer p = drm_dbg_printer(mgr->dev,
DRM_UT_DP,
DBG_PREFIX);
/* 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;
}
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;
}
/*
* 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;
/*
* 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;
/*
* 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;
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);
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));
drm_modeset_lock(&mgr->base.lock, NULL);
state = to_drm_dp_mst_topology_state(mgr->base.state);
payload = drm_atomic_get_mst_payload_state(state, port);
/*
* "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;
/* it's okay for these to fail */
if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_REMOTE) {
drm_dp_payload_send_msg(mgr, payload->port, payload->vcpi, 0);
payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_DFP;
}
if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_DFP)
drm_dp_dpcd_write_payload(mgr->aux, payload->vcpi, payload->vc_start_slot, 0);
}
/**
* drm_dp_add_payload_part1() - Execute payload update part 1
* @mgr: Manager to use.
* @mst_state: The MST atomic state
* @payload: The payload to write
*
* Determines the starting time slot for the given payload, and programs the VCPI for this payload
* into the DPCD of DPRX. After calling this, the driver should generate ACT and payload packets.
*
* Returns: 0 on success, error code on failure.
*/
int drm_dp_add_payload_part1(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_topology_state *mst_state,
struct drm_dp_mst_atomic_payload *payload)
{
struct drm_dp_mst_port *port;
int ret;
/* Update mst mgr info */
if (mgr->payload_count == 0)
mgr->next_start_slot = mst_state->start_slot;
/* Allocate payload to immediate downstream facing port */
port = drm_dp_mst_topology_get_port_validated(mgr, payload->port);
if (!port) {
drm_dbg_kms(mgr->dev,
"VCPI %d for port %p not in topology, not creating a payload to remote\n",
payload->vcpi, payload->port);
return -EIO;
}
ret = drm_dp_create_payload_at_dfp(mgr, payload);
if (ret < 0) {
drm_dbg_kms(mgr->dev, "Failed to create MST payload for port %p: %d\n",
payload->port, ret);
goto put_port;
}
/**
* drm_dp_remove_payload_part1() - Remove an MST payload along the virtual channel
* @mgr: Manager to use.
* @mst_state: The MST atomic state
* @payload: The payload to remove
*
* Removes a payload along the virtual channel if it was successfully allocated.
* After calling this, the driver should set HW to generate ACT and then switch to new
* payload allocation state.
*/
void drm_dp_remove_payload_part1(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_topology_state *mst_state,
struct drm_dp_mst_atomic_payload *payload)
{
/* Remove remote payload allocation */
bool send_remove = false;
if (send_remove)
drm_dp_destroy_payload_at_remote_and_dfp(mgr, mst_state, payload);
else
drm_dbg_kms(mgr->dev, "Payload for VCPI %d not in topology, not sending remove\n",
payload->vcpi);
/**
* drm_dp_remove_payload_part2() - Remove an MST payload locally
* @mgr: Manager to use.
* @mst_state: The MST atomic state
* @old_payload: The payload with its old state
* @new_payload: The payload with its latest state
*
* Updates the starting time slots of all other payloads which would have been shifted towards
* the start of the payload ID table as a result of removing a payload. Driver should call this
* function whenever it removes a payload in its HW. It's independent to the result of payload
* allocation/deallocation at branch devices along the virtual channel.
*/
void drm_dp_remove_payload_part2(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_topology_state *mst_state,
const struct drm_dp_mst_atomic_payload *old_payload,
struct drm_dp_mst_atomic_payload *new_payload)
{
struct drm_dp_mst_atomic_payload *pos;
if (new_payload->delete)
drm_dp_mst_put_port_malloc(new_payload->port);
new_payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_NONE;
}
EXPORT_SYMBOL(drm_dp_remove_payload_part2);
/**
* drm_dp_add_payload_part2() - Execute payload update part 2
* @mgr: Manager to use.
* @payload: The payload to update
*
* If @payload was successfully assigned a starting time slot by drm_dp_add_payload_part1(), this
* function will send the sideband messages to finish allocating this payload.
*
* Returns: 0 on success, negative error code on failure.
*/
int drm_dp_add_payload_part2(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_atomic_payload *payload)
{
int ret = 0;
/* Skip failed payloads */
if (payload->payload_allocation_status != DRM_DP_MST_PAYLOAD_ALLOCATION_DFP) {
drm_dbg_kms(mgr->dev, "Part 1 of payload creation for %s failed, skipping part 2\n",
payload->port->connector->name);
return -EIO;
}
/* Allocate payload to remote end */
ret = drm_dp_create_payload_to_remote(mgr, payload);
if (ret < 0)
drm_err(mgr->dev, "Step 2 of creating MST payload for %p failed: %d\n",
payload->port, ret);
else
payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_REMOTE;
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;
}
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 MTP link
* @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.
*
* Returns the BW / timeslot value in 20.12 fixed point format.
*/
fixed20_12 drm_dp_get_vc_payload_bw(int link_rate, int link_lane_count)
{
int ch_coding_efficiency =
drm_dp_bw_channel_coding_efficiency(drm_dp_is_uhbr_rate(link_rate));
fixed20_12 ret;
/**
* drm_dp_read_mst_cap() - Read the sink's MST mode capability
* @aux: The DP AUX channel to use
* @dpcd: A cached copy of the DPCD capabilities for this sink
*
* Returns: enum drm_dp_mst_mode to indicate MST mode capability
*/
enum drm_dp_mst_mode 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 DRM_DP_SST;
if (drm_dp_dpcd_read_byte(aux, DP_MSTM_CAP, &mstm_cap) < 0)
return DRM_DP_SST;
if (mstm_cap & DP_MST_CAP)
return DRM_DP_MST;
if (mstm_cap & DP_SINGLE_STREAM_SIDEBAND_MSG)
return DRM_DP_SST_SIDEBAND_MSG;
/**
* 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->lock);
if (mst_state == mgr->mst_state)
goto out_unlock;
mgr->mst_state = mst_state;
/* set the device into MST mode */
if (mst_state) {
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;
}
/* 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_write_byte(mgr->aux, DP_MSTM_CTRL,
DP_MST_EN |
DP_UP_REQ_EN |
DP_UPSTREAM_IS_SRC);
if (ret < 0)
goto out_unlock;
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_write_byte(mgr->aux, DP_MSTM_CTRL, 0);
ret = 0;
mgr->payload_id_table_cleared = false;
mgr->reset_rx_state = true;
}
out_unlock:
mutex_unlock(&mgr->lock);
if (mstb)
drm_dp_mst_topology_put_mstb(mstb);
return ret;
/* 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_queue_probe - Queue a topology probe
* @mgr: manager to probe
*
* Queue a work to probe the MST topology. Driver's should call this only to
* sync the topology's HW->SW state after the MST link's parameters have
* changed in a way the state could've become out-of-sync. This is the case
* for instance when the link rate between the source and first downstream
* branch device has switched between UHBR and non-UHBR rates. Except of those
* cases - for instance when a sink gets plugged/unplugged to a port - the SW
* state will get updated automatically via MST UP message notifications.
*/
void drm_dp_mst_topology_queue_probe(struct drm_dp_mst_topology_mgr *mgr)
{
mutex_lock(&mgr->lock);
if (drm_WARN_ON(mgr->dev, !mgr->mst_state || !mgr->mst_primary))
goto out_unlock;
/**
* 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_write_byte(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)
{
u8 buf[UUID_SIZE];
guid_t guid;
int ret;
mutex_lock(&mgr->lock);
if (!mgr->mst_primary)
goto out_fail;
if (drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd) < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
goto out_fail;
}
ret = drm_dp_dpcd_write_byte(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_data(mgr->aux, DP_GUID, buf, sizeof(buf));
if (ret < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
goto out_fail;
}
import_guid(&guid, buf);
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
*/
drm_dp_mst_queue_probe_work(mgr);
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);
}
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;
}
/**
* drm_dp_mst_hpd_irq_handle_event() - MST hotplug IRQ handle MST event
* @mgr: manager to notify irq for.
* @esi: 4 bytes from SINK_COUNT_ESI
* @ack: 4 bytes used to ack events starting from SINK_COUNT_ESI
* @handled: whether the hpd interrupt was consumed or not
*
* This should be called from the driver when it detects a HPD IRQ,
* along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The
* topology manager will process the sideband messages received
* as indicated in the DEVICE_SERVICE_IRQ_VECTOR_ESI0 and set the
* corresponding flags that Driver has to ack the DP receiver later.
*
* Note that driver shall also call
* drm_dp_mst_hpd_irq_send_new_request() if the 'handled' is set
* after calling this function, to try to kick off a new request in
* the queue if the previous message transaction is completed.
*
* See also:
* drm_dp_mst_hpd_irq_send_new_request()
*/
int drm_dp_mst_hpd_irq_handle_event(struct drm_dp_mst_topology_mgr *mgr, const u8 *esi,
u8 *ack, bool *handled)
{
int ret = 0;
int sc;
*handled = false;
sc = DP_GET_SINK_COUNT(esi[0]);
/**
* drm_dp_mst_hpd_irq_send_new_request() - MST hotplug IRQ kick off new request
* @mgr: manager to notify irq for.
*
* This should be called from the driver when mst irq event is handled
* and acked. Note that new down request should only be sent when
* previous message transaction is completed. Source is not supposed to generate
* interleaved message transactions.
*/
void drm_dp_mst_hpd_irq_send_new_request(struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_dp_sideband_msg_tx *txmsg;
bool kick = true;
mutex_lock(&mgr->qlock);
txmsg = list_first_entry_or_null(&mgr->tx_msg_downq,
struct drm_dp_sideband_msg_tx, next);
/* If last transaction is not completed yet*/
if (!txmsg ||
txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND ||
txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
kick = false;
mutex_unlock(&mgr->qlock);
if (kick)
drm_dp_mst_kick_tx(mgr);
}
EXPORT_SYMBOL(drm_dp_mst_hpd_irq_send_new_request);
/**
* 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 (drm_dp_mst_port_is_logical(port) && !port->cached_edid)
port->cached_edid = drm_edid_read_ddc(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_edid_read() - 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.
*/
const struct drm_edid *drm_dp_mst_edid_read(struct drm_connector *connector,
struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port)
{
const struct drm_edid *drm_edid;
/* 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)
drm_edid = drm_edid_dup(port->cached_edid);
else
drm_edid = drm_edid_read_ddc(connector, &port->aux.ddc);
/**
* 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 function is deprecated; please use drm_dp_mst_edid_read() instead.
*
* 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)
{
const struct drm_edid *drm_edid;
struct edid *edid;
/**
* drm_dp_atomic_find_time_slots() - Find and add time slots to the state
* @state: global atomic state
* @mgr: MST topology manager for the port
* @port: port to find time slots for
* @pbn: bandwidth required for the mode in PBN
*
* Allocates time slots to @port, replacing any previous time slot allocations it may
* have had. Any atomic drivers which support MST must call this function in
* their &drm_encoder_helper_funcs.atomic_check() callback unconditionally to
* change the current time slot allocation for the new state, and ensure the MST
* atomic state is added whenever the state of payloads in the topology changes.
*
* 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_time_slots() in the same atomic check phase.
*
* See also:
* drm_dp_atomic_release_time_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_time_slots(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, int pbn)
{
struct drm_dp_mst_topology_state *topology_state;
struct drm_dp_mst_atomic_payload *payload = NULL;
struct drm_connector_state *conn_state;
int prev_slots = 0, prev_bw = 0, 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 */
payload = drm_atomic_get_mst_payload_state(topology_state, port);
if (payload) {
prev_slots = payload->time_slots;
prev_bw = payload->pbn;
/*
* This should never happen, unless the driver tries
* releasing and allocating the same timeslot allocation,
* which is an error
*/
if (drm_WARN_ON(mgr->dev, payload->delete)) {
drm_err(mgr->dev,
"cannot allocate and release time slots on [MST PORT:%p] in the same state\n",
port);
return -EINVAL;
}
}
/* Add the new allocation to the state, note the VCPI isn't assigned until the end */
if (!payload) {
payload = kzalloc(sizeof(*payload), GFP_KERNEL);
if (!payload)
return -ENOMEM;
/**
* drm_dp_atomic_release_time_slots() - Release allocated time slots
* @state: global atomic state
* @mgr: MST topology manager for the port
* @port: The port to release the time slots from
*
* Releases any time slots that have been allocated to a port in the atomic
* state. Any atomic drivers which support MST must call this function
* unconditionally in their &drm_connector_helper_funcs.atomic_check() callback.
* This helper will check whether time slots would be released by the new state and
* respond accordingly, along with ensuring the MST state is always added to the
* atomic state whenever a new state would modify the state of payloads on the
* topology.
*
* 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_time_slots() on the same @port in a single atomic check
* phase.
*
* See also:
* drm_dp_atomic_find_time_slots()
* drm_dp_mst_atomic_check()
*
* Returns:
* 0 on success, negative error code otherwise
*/
int drm_dp_atomic_release_time_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_mst_atomic_payload *payload;
struct drm_connector_state *old_conn_state, *new_conn_state;
bool update_payload = true;
old_conn_state = drm_atomic_get_old_connector_state(state, port->connector);
if (!old_conn_state->crtc)
return 0;
/* If the CRTC isn't disabled by this state, don't release it's payload */
new_conn_state = drm_atomic_get_new_connector_state(state, port->connector);
if (new_conn_state->crtc) {
struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
/* No modeset means no payload changes, so it's safe to not pull in the MST state */
if (!crtc_state || !drm_atomic_crtc_needs_modeset(crtc_state))
return 0;
if (!crtc_state->mode_changed && !crtc_state->connectors_changed)
update_payload = false;
}
topology_state = drm_atomic_get_mst_topology_state(state, mgr);
if (IS_ERR(topology_state))
return PTR_ERR(topology_state);
topology_state->pending_crtc_mask |= drm_crtc_mask(old_conn_state->crtc);
if (!update_payload)
return 0;
payload = drm_atomic_get_mst_payload_state(topology_state, port);
if (WARN_ON(!payload)) {
drm_err(mgr->dev, "No payload for [MST PORT:%p] found in mst state %p\n",
port, &topology_state->base);
return -EINVAL;
}
/**
* drm_dp_mst_atomic_setup_commit() - setup_commit hook for MST helpers
* @state: global atomic state
*
* This function saves all of the &drm_crtc_commit structs in an atomic state that touch any CRTCs
* currently assigned to an MST topology. Drivers must call this hook from their
* &drm_mode_config_helper_funcs.atomic_commit_setup hook.
*
* Returns:
* 0 if all CRTC commits were retrieved successfully, negative error code otherwise
*/
int drm_dp_mst_atomic_setup_commit(struct drm_atomic_state *state)
{
struct drm_dp_mst_topology_mgr *mgr;
struct drm_dp_mst_topology_state *mst_state;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
int i, j, commit_idx, num_commit_deps;
for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) {
if (!mst_state->pending_crtc_mask)
continue;
/**
* drm_dp_mst_atomic_wait_for_dependencies() - Wait for all pending commits on MST topologies,
* prepare new MST state for commit
* @state: global atomic state
*
* Goes through any MST topologies in this atomic state, and waits for any pending commits which
* touched CRTCs that were/are on an MST topology to be programmed to hardware and flipped to before
* returning. This is to prevent multiple non-blocking commits affecting an MST topology from racing
* with eachother by forcing them to be executed sequentially in situations where the only resources
* the modeset objects in these commits share are an MST topology.
*
* This function also prepares the new MST state for commit by performing some state preparation
* which can't be done until this point, such as reading back the final VC start slots (which are
* determined at commit-time) from the previous state.
*
* All MST drivers must call this function after calling drm_atomic_helper_wait_for_dependencies(),
* or whatever their equivalent of that is.
*/
void drm_dp_mst_atomic_wait_for_dependencies(struct drm_atomic_state *state)
{
struct drm_dp_mst_topology_state *old_mst_state, *new_mst_state;
struct drm_dp_mst_topology_mgr *mgr;
struct drm_dp_mst_atomic_payload *old_payload, *new_payload;
int i, j, ret;
for_each_oldnew_mst_mgr_in_state(state, mgr, old_mst_state, new_mst_state, i) {
for (j = 0; j < old_mst_state->num_commit_deps; j++) {
ret = drm_crtc_commit_wait(old_mst_state->commit_deps[j]);
if (ret < 0)
drm_err(state->dev, "Failed to wait for %s: %d\n",
old_mst_state->commit_deps[j]->crtc->name, ret);
}
/* Now that previous state is committed, it's safe to copy over the start slot
* and allocation status assignments
*/
list_for_each_entry(old_payload, &old_mst_state->payloads, next) {
if (old_payload->delete)
continue;
/**
* drm_dp_mst_root_conn_atomic_check() - Serialize CRTC commits on MST-capable connectors operating
* in SST mode
* @new_conn_state: The new connector state of the &drm_connector
* @mgr: The MST topology manager for the &drm_connector
*
* Since MST uses fake &drm_encoder structs, the generic atomic modesetting code isn't able to
* serialize non-blocking commits happening on the real DP connector of an MST topology switching
* into/away from MST mode - as the CRTC on the real DP connector and the CRTCs on the connector's
* MST topology will never share the same &drm_encoder.
*
* This function takes care of this serialization issue, by checking a root MST connector's atomic
* state to determine if it is about to have a modeset - and then pulling in the MST topology state
* if so, along with adding any relevant CRTCs to &drm_dp_mst_topology_state.pending_crtc_mask.
*
* Drivers implementing MST must call this function from the
* &drm_connector_helper_funcs.atomic_check hook of any physical DP &drm_connector capable of
* driving MST sinks.
*
* Returns:
* 0 on success, negative error code otherwise
*/
int drm_dp_mst_root_conn_atomic_check(struct drm_connector_state *new_conn_state,
struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_atomic_state *state = new_conn_state->state;
struct drm_connector_state *old_conn_state =
drm_atomic_get_old_connector_state(state, new_conn_state->connector);
struct drm_crtc_state *crtc_state;
struct drm_dp_mst_topology_state *mst_state = NULL;
if (new_conn_state->crtc) {
crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
if (crtc_state && drm_atomic_crtc_needs_modeset(crtc_state)) {
mst_state = drm_atomic_get_mst_topology_state(state, mgr);
if (IS_ERR(mst_state))
return PTR_ERR(mst_state);
/**
* 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_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 of 3 seconds.
*/
return drm_dp_dpcd_poll_act_handled(mgr->aux, 3000);
}
EXPORT_SYMBOL(drm_dp_check_act_status);
/**
* drm_dp_calc_pbn_mode() - Calculate the PBN for a mode.
* @clock: dot clock
* @bpp: bpp as .4 binary fixed point
*
* 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)
{
/*
* 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 = clock * bpp / 16
* peak_kbps *= SSC overhead / 1000000
* peak_kbps /= 8 convert to Kbytes
* peak_kBps *= (64/54) / 1000 convert to PBN
*/
/*
* TODO: Use the actual link and mode parameters to calculate
* the overhead. For now it's assumed that these are
* 4 link lanes, 4096 hactive pixels, which don't add any
* significant data padding overhead and that there is no DSC
* or FEC overhead.
*/
int overhead = drm_dp_bw_overhead(4, 4096, 0, bpp,
DRM_DP_BW_OVERHEAD_MST |
DRM_DP_BW_OVERHEAD_SSC_REF_CLK);
/* 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';
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_data(mgr->aux,
DP_PAYLOAD_TABLE_UPDATE_STATUS + i,
&buf[i], 16) < 0)
return false;
}
return true;
}
/**
* 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)
{
struct drm_dp_mst_topology_state *state;
struct drm_dp_mst_atomic_payload *payload;
int i, ret;
/*
* 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;
list_for_each_entry_safe(pos, tmp, &mst_state->payloads, next) {
/* We only keep references to ports with active payloads */
if (!pos->delete)
drm_dp_mst_put_port_malloc(pos->port);
kfree(pos);
}
for (i = 0; i < mst_state->num_commit_deps; i++)
drm_crtc_commit_put(mst_state->commit_deps[i]);
/**
* drm_dp_mst_port_downstream_of_parent - check if a port is downstream of a parent port
* @mgr: MST topology manager
* @port: the port being looked up
* @parent: the parent port
*
* The function returns %true if @port is downstream of @parent. If @parent is
* %NULL - denoting the root port - the function returns %true if @port is in
* @mgr's topology.
*/
bool
drm_dp_mst_port_downstream_of_parent(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port,
struct drm_dp_mst_port *parent)
{
bool ret;
mutex_lock(&mgr->lock);
ret = drm_dp_mst_port_downstream_of_parent_locked(mgr, port, parent);
mutex_unlock(&mgr->lock);
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_mst_port **failing_port)
{
struct drm_dp_mst_atomic_payload *payload;
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(payload, &state->payloads, next) {
if (!payload->pbn ||
!drm_dp_mst_port_downstream_of_branch(payload->port, mstb))
continue;
list_for_each_entry(port, &mstb->ports, next) {
ret = drm_dp_mst_atomic_check_port_bw_limit(port, state, failing_port);
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_mst_port **failing_port)
{
struct drm_dp_mst_atomic_payload *payload;
int pbn_used = 0;
if (port->pdt == DP_PEER_DEVICE_NONE)
return 0;
if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
payload = drm_atomic_get_mst_payload_state(state, port);
if (!payload)
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);
*failing_port = port;
return -EINVAL;
}
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);
*failing_port = port;
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_payload_alloc_limits(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_topology_state *mst_state)
{
struct drm_dp_mst_atomic_payload *payload;
int avail_slots = mst_state->total_avail_slots, payload_count = 0;
list_for_each_entry(payload, &mst_state->payloads, next) {
/* Releasing payloads is always OK-even if the port is gone */
if (payload->delete) {
drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all time slots\n",
payload->port);
continue;
}
drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d time slots\n",
payload->port, payload->time_slots);
avail_slots -= payload->time_slots;
if (avail_slots < 0) {
drm_dbg_atomic(mgr->dev,
"[MST PORT:%p] not enough time slots in mst state %p (avail=%d)\n",
payload->port, mst_state, avail_slots + payload->time_slots);
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;
}
if (!payload_count)
mst_state->pbn_div.full = dfixed_const(0);
drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p TU pbn_div=%d avail=%d used=%d\n",
mgr, mst_state, dfixed_trunc(mst_state->pbn_div), 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_mst_atomic_payload *pos;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
/**
* 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
* @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, bool enable)
{
struct drm_dp_mst_topology_state *mst_state;
struct drm_dp_mst_atomic_payload *payload;
int time_slots = 0;
mst_state = drm_atomic_get_mst_topology_state(state, port->mgr);
if (IS_ERR(mst_state))
return PTR_ERR(mst_state);
payload = drm_atomic_get_mst_payload_state(mst_state, port);
if (!payload) {
drm_dbg_atomic(state->dev,
"[MST PORT:%p] Couldn't find payload in mst state %p\n",
port, mst_state);
return -EINVAL;
}
if (payload->dsc_enabled == enable) {
drm_dbg_atomic(state->dev,
"[MST PORT:%p] DSC flag is already set to %d, returning %d time slots\n",
port, enable, payload->time_slots);
time_slots = payload->time_slots;
}
if (enable) {
time_slots = drm_dp_atomic_find_time_slots(state, port->mgr, port, pbn);
drm_dbg_atomic(state->dev,
"[MST PORT:%p] Enabling DSC flag, reallocating %d time slots on the port\n",
port, time_slots);
if (time_slots < 0)
return -EINVAL;
}
/**
* drm_dp_mst_atomic_check_mgr - Check the atomic state of an MST topology manager
* @state: The global atomic state
* @mgr: Manager to check
* @mst_state: The MST atomic state for @mgr
* @failing_port: Returns the port with a BW limitation
*
* Checks the given MST manager's topology state for an atomic update to ensure
* that it's valid. This includes checking whether there's enough bandwidth to
* support the new timeslot allocations in the atomic update.
*
* Any atomic drivers supporting DP MST must make sure to call this or
* the drm_dp_mst_atomic_check() function after checking the rest of their state
* in their &drm_mode_config_funcs.atomic_check() callback.
*
* See also:
* drm_dp_mst_atomic_check()
* drm_dp_atomic_find_time_slots()
* drm_dp_atomic_release_time_slots()
*
* Returns:
* - 0 if the new state is valid
* - %-ENOSPC, if the new state is invalid, because of BW limitation
* @failing_port is set to:
*
* - The non-root port where a BW limit check failed
* with all the ports downstream of @failing_port passing
* the BW limit check.
* The returned port pointer is valid until at least
* one payload downstream of it exists.
* - %NULL if the BW limit check failed at the root port
* with all the ports downstream of the root port passing
* the BW limit check.
*
* - %-EINVAL, if the new state is invalid, because the root port has
* too many payloads.
*/
int drm_dp_mst_atomic_check_mgr(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_topology_state *mst_state,
struct drm_dp_mst_port **failing_port)
{
int ret;
*failing_port = NULL;
if (!mgr->mst_state)
return 0;
mutex_lock(&mgr->lock);
ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary,
mst_state,
failing_port);
mutex_unlock(&mgr->lock);
/**
* 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, calling drm_dp_mst_atomic_check_mgr() for all MST manager in the
* atomic state. This includes checking whether there's enough bandwidth to
* support the new timeslot 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_mst_atomic_check_mgr()
* drm_dp_atomic_find_time_slots()
* drm_dp_atomic_release_time_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;
/**
* 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.
*
* 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_atomic_get_old_mst_topology_state: get old MST topology state in atomic state, if any
* @state: global atomic state
* @mgr: MST topology manager, also the private object in this case
*
* This function wraps drm_atomic_get_old_private_obj_state() passing in the MST atomic
* state vtable so that the private object state returned is that of a MST
* topology object.
*
* Returns:
* The old MST topology state, or NULL if there's no topology state for this MST mgr
* in the global atomic state
*/
struct drm_dp_mst_topology_state *
drm_atomic_get_old_mst_topology_state(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_private_state *old_priv_state =
drm_atomic_get_old_private_obj_state(state, &mgr->base);
/**
* drm_atomic_get_new_mst_topology_state: get new MST topology state in atomic state, if any
* @state: global atomic state
* @mgr: MST topology manager, also the private object in this case
*
* This function wraps drm_atomic_get_new_private_obj_state() passing in the MST atomic
* state vtable so that the private object state returned is that of a MST
* topology object.
*
* Returns:
* The new MST topology state, or NULL if there's no topology state for this MST mgr
* in the global atomic state
*/
struct drm_dp_mst_topology_state *
drm_atomic_get_new_mst_topology_state(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_private_state *new_priv_state =
drm_atomic_get_new_private_obj_state(state, &mgr->base);
/**
* 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
* @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 conn_base_id)
{
struct drm_dp_mst_topology_state *mst_state;
/*
* 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;
/**
* 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.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;
/**
* 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;
/**
* drm_dp_mst_aux_for_parent() - Get the AUX device for an MST port's parent
* @port: MST port whose parent's AUX device is returned
*
* Return the AUX device for @port's parent or NULL if port's parent is the
* root port.
*/
struct drm_dp_aux *drm_dp_mst_aux_for_parent(struct drm_dp_mst_port *port)
{
if (!port->parent || !port->parent->port_parent)
return NULL;
/**
* 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_aux *immediate_upstream_aux;
struct drm_dp_mst_port *fec_port;
struct drm_dp_desc desc = {};
u8 upstream_dsc;
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)) {
if (drm_dp_dpcd_read_data(&port->aux,
DP_DSC_SUPPORT, &endpoint_dsc, 1) < 0)
return NULL;
if (drm_dp_dpcd_read_data(&port->aux,
DP_FEC_CAPABILITY, &endpoint_fec, 1) < 0)
return NULL;
if (drm_dp_dpcd_read_data(&immediate_upstream_port->aux,
DP_DSC_SUPPORT, &upstream_dsc, 1) < 0)
return NULL;
/* 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 (immediate_upstream_port)
immediate_upstream_aux = &immediate_upstream_port->aux;
else
immediate_upstream_aux = port->mgr->aux;
if (drm_dp_read_desc(immediate_upstream_aux, &desc, true))
return NULL;
if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD)) {
u8 dpcd_ext[DP_RECEIVER_CAP_SIZE];
if (drm_dp_dpcd_read_data(immediate_upstream_aux,
DP_DSC_SUPPORT, &upstream_dsc, 1) < 0)
return NULL;
if (!(upstream_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED))
return NULL;
if (drm_dp_read_dpcd_caps(immediate_upstream_aux, dpcd_ext) < 0)
return NULL;
/*
* 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_data(&port->aux,
DP_DSC_SUPPORT, &endpoint_dsc, 1) < 0)
return NULL;
if (drm_dp_dpcd_read_data(&port->aux,
DP_FEC_CAPABILITY, &endpoint_fec, 1) < 0)
return NULL;
if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
(endpoint_fec & DP_FEC_CAPABLE))
return &port->aux;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.