/** * DOC: dp helpers * * These functions contain some common logic and helpers at various abstraction * levels to deal with Display Port sink devices and related things like DP aux * channel transfers, EDID reading over DP aux channels, decoding certain DPCD * blocks, ...
*/
/* Helpers for DP link training */ static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r)
{ return link_status[r - DP_LANE0_1_STATUS];
}
static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE], int lane)
{ int i = DP_LANE0_1_STATUS + (lane >> 1); int s = (lane & 1) * 4;
u8 l = dp_link_status(link_status, i);
return (l >> s) & 0xf;
}
bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], int lane_count)
{
u8 lane_align;
u8 lane_status; int lane;
lane_align = dp_link_status(link_status,
DP_LANE_ALIGN_STATUS_UPDATED); if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) returnfalse; for (lane = 0; lane < lane_count; lane++) {
lane_status = dp_get_lane_status(link_status, lane); if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) returnfalse;
} returntrue;
}
EXPORT_SYMBOL(drm_dp_channel_eq_ok);
bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], int lane_count)
{ int lane;
u8 lane_status;
for (lane = 0; lane < lane_count; lane++) {
lane_status = dp_get_lane_status(link_status, lane); if ((lane_status & DP_LANE_CR_DONE) == 0) returnfalse;
} returntrue;
}
EXPORT_SYMBOL(drm_dp_clock_recovery_ok);
u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], int lane)
{ int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); int s = ((lane & 1) ?
DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
u8 l = dp_link_status(link_status, i);
u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], int lane)
{ int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); int s = ((lane & 1) ?
DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
u8 l = dp_link_status(link_status, i);
staticint __128b132b_channel_eq_delay_us(conststruct drm_dp_aux *aux, u8 rd_interval)
{ switch (rd_interval) { default:
drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n",
aux->name, rd_interval);
fallthrough; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US: return 400; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS: return 4000; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS: return 8000; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS: return 12000; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS: return 16000; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS: return 32000; case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS: return 64000;
}
}
/* * The link training delays are different for: * * - Clock recovery vs. channel equalization * - DPRX vs. LTTPR * - 128b/132b vs. 8b/10b * - DPCD rev 1.3 vs. later * * Get the correct delay in us, reading DPCD if necessary.
*/ staticint __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], enum drm_dp_phy dp_phy, bool uhbr, bool cr)
{ int (*parse)(conststruct drm_dp_aux *aux, u8 rd_interval); unsignedint offset;
u8 rd_interval, mask;
if (dp_phy == DP_PHY_DPRX) { if (uhbr) { if (cr) return 100;
/** * drm_dp_phy_name() - Get the name of the given DP PHY * @dp_phy: The DP PHY identifier * * Given the @dp_phy, get a user friendly name of the DP PHY, either "DPRX" or * "LTTPR <N>", or "<INVALID DP PHY>" on errors. The returned string is always * non-NULL and valid. * * Returns: Name of the DP PHY.
*/ constchar *drm_dp_phy_name(enum drm_dp_phy dp_phy)
{ staticconstchar * const phy_names[] = {
[DP_PHY_DPRX] = "DPRX",
[DP_PHY_LTTPR1] = "LTTPR 1",
[DP_PHY_LTTPR2] = "LTTPR 2",
[DP_PHY_LTTPR3] = "LTTPR 3",
[DP_PHY_LTTPR4] = "LTTPR 4",
[DP_PHY_LTTPR5] = "LTTPR 5",
[DP_PHY_LTTPR6] = "LTTPR 6",
[DP_PHY_LTTPR7] = "LTTPR 7",
[DP_PHY_LTTPR8] = "LTTPR 8",
};
/** * drm_dp_lttpr_wake_timeout_setup() - Grant extended time for sink to wake up * @aux: The DP AUX channel to use * @transparent_mode: This is true if lttpr is in transparent mode * * This function checks if the sink needs any extended wake time, if it does * it grants this request. Post this setup the source device can keep trying * the Aux transaction till the granted wake timeout. * If this function is not called all Aux transactions are expected to take * a default of 1ms before they throw an error.
*/ void drm_dp_lttpr_wake_timeout_setup(struct drm_dp_aux *aux, bool transparent_mode)
{
u8 val = 1; int ret;
if (ret > 0)
drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n",
aux->name, offset, arrow, ret, min(ret, 20), buffer); else
drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n",
aux->name, offset, arrow, ret);
}
/** * DOC: dp helpers * * The DisplayPort AUX channel is an abstraction to allow generic, driver- * independent access to AUX functionality. Drivers can take advantage of * this by filling in the fields of the drm_dp_aux structure. * * Transactions are described using a hardware-independent drm_dp_aux_msg * structure, which is passed into a driver's .transfer() implementation. * Both native and I2C-over-AUX transactions are supported.
*/
/* * If the device attached to the aux bus is powered down then there's * no reason to attempt a transfer. Error out immediately.
*/ if (aux->powered_down) {
ret = -EBUSY; goto unlock;
}
/* * The specification doesn't give any recommendation on how often to * retry native transactions. We used to retry 7 times like for * aux i2c transactions but real world devices this wasn't * sufficient, bump to 32 which makes Dell 4k monitors happier.
*/ for (retry = 0; retry < 32; retry++) { if (ret != 0 && ret != -ETIMEDOUT) {
usleep_range(AUX_RETRY_INTERVAL,
AUX_RETRY_INTERVAL + 100);
}
ret = aux->transfer(aux, &msg); if (ret >= 0) {
native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK; if (native_reply == DP_AUX_NATIVE_REPLY_ACK) { if (ret == size) goto unlock;
ret = -EPROTO;
} else
ret = -EIO;
}
/* * We want the error we return to be the error we received on * the first transaction, since we may get a different error the * next time we retry
*/ if (!err)
err = ret;
}
drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n",
aux->name, err);
ret = err;
/** * drm_dp_dpcd_probe() - probe a given DPCD address with a 1-byte read access * @aux: DisplayPort AUX channel (SST) * @offset: address of the register to probe * * Probe the provided DPCD address by reading 1 byte from it. The function can * be used to trigger some side-effect the read access has, like waking up the * sink, without the need for the read-out value. * * Returns 0 if the read access suceeded, or a negative error code on failure.
*/ int drm_dp_dpcd_probe(struct drm_dp_aux *aux, unsignedint offset)
{
u8 buffer; int ret;
ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, 1);
WARN_ON(ret == 0);
return ret < 0 ? ret : 0;
}
EXPORT_SYMBOL(drm_dp_dpcd_probe);
/** * drm_dp_dpcd_set_powered() - Set whether the DP device is powered * @aux: DisplayPort AUX channel; for convenience it's OK to pass NULL here * and the function will be a no-op. * @powered: true if powered; false if not * * If the endpoint device on the DP AUX bus is known to be powered down * then this function can be called to make future transfers fail immediately * instead of needing to time out. * * If this function is never called then a device defaults to being powered.
*/ void drm_dp_dpcd_set_powered(struct drm_dp_aux *aux, bool powered)
{ if (!aux) return;
/** * drm_dp_dpcd_set_probe() - Set whether a probing before DPCD access is done * @aux: DisplayPort AUX channel * @enable: Enable the probing if required
*/ void drm_dp_dpcd_set_probe(struct drm_dp_aux *aux, bool enable)
{
WRITE_ONCE(aux->dpcd_probe_disabled, !enable);
}
EXPORT_SYMBOL(drm_dp_dpcd_set_probe);
staticbool dpcd_access_needs_probe(struct drm_dp_aux *aux)
{ /* * HP ZR24w corrupts the first DPCD access after entering power save * mode. Eg. on a read, the entire buffer will be filled with the same * byte. Do a throw away read to avoid corrupting anything we care * about. Afterwards things will work correctly until the monitor * gets woken up and subsequently re-enters power save mode. * * The user pressing any button on the monitor is enough to wake it * up, so there is no particularly good place to do the workaround. * We just have to do it before any DPCD access and hope that the * monitor doesn't power down exactly after the throw away read.
*/ return !aux->is_remote && !READ_ONCE(aux->dpcd_probe_disabled);
}
/** * drm_dp_dpcd_read() - read a series of bytes from the DPCD * @aux: DisplayPort AUX channel (SST or MST) * @offset: address of the (first) register to read * @buffer: buffer to store the register values * @size: number of bytes in @buffer * * Returns the number of bytes transferred on success, or a negative error * code on failure. -EIO is returned if the request was NAKed by the sink or * if the retry count was exceeded. If not all bytes were transferred, this * function returns -EPROTO. Errors from the underlying AUX channel transfer * function, with the exception of -EBUSY (which causes the transaction to * be retried), are propagated to the caller. * * In most of the cases you want to use drm_dp_dpcd_read_data() instead.
*/
ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsignedint offset, void *buffer, size_t size)
{ int ret;
if (dpcd_access_needs_probe(aux)) {
ret = drm_dp_dpcd_probe(aux, DP_TRAINING_PATTERN_SET); if (ret < 0) return ret;
}
if (aux->is_remote)
ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size); else
ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset,
buffer, size);
/** * drm_dp_dpcd_write() - write a series of bytes to the DPCD * @aux: DisplayPort AUX channel (SST or MST) * @offset: address of the (first) register to write * @buffer: buffer containing the values to write * @size: number of bytes in @buffer * * Returns the number of bytes transferred on success, or a negative error * code on failure. -EIO is returned if the request was NAKed by the sink or * if the retry count was exceeded. If not all bytes were transferred, this * function returns -EPROTO. Errors from the underlying AUX channel transfer * function, with the exception of -EBUSY (which causes the transaction to * be retried), are propagated to the caller. * * In most of the cases you want to use drm_dp_dpcd_write_data() instead.
*/
ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsignedint offset, void *buffer, size_t size)
{ int ret;
if (aux->is_remote)
ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size); else
ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset,
buffer, size);
/** * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207) * @aux: DisplayPort AUX channel * @status: buffer to store the link status in (must be at least 6 bytes) * * Returns a negative error code on failure or 0 on success.
*/ int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux,
u8 status[DP_LINK_STATUS_SIZE])
{ return drm_dp_dpcd_read_data(aux, DP_LANE0_1_STATUS, status,
DP_LINK_STATUS_SIZE);
}
EXPORT_SYMBOL(drm_dp_dpcd_read_link_status);
/** * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY * @aux: DisplayPort AUX channel * @dp_phy: the DP PHY to get the link status for * @link_status: buffer to return the status in * * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The * layout of the returned @link_status matches the DPCD register layout of the * DPRX PHY link status. * * Returns 0 if the information was read successfully or a negative error code * on failure.
*/ 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])
{ int ret;
if (dp_phy == DP_PHY_DPRX) return drm_dp_dpcd_read_data(aux,
DP_LANE0_1_STATUS,
link_status,
DP_LINK_STATUS_SIZE);
ret = drm_dp_dpcd_read_data(aux,
DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy),
link_status,
DP_LINK_STATUS_SIZE - 1);
if (ret < 0) return ret;
/* Convert the LTTPR to the sink PHY link status layout */
memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1],
&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS],
DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1);
link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0;
/** * drm_dp_link_power_up() - power up a DisplayPort link * @aux: DisplayPort AUX channel * @revision: DPCD revision supported on the link * * Returns 0 on success or a negative error code on failure.
*/ int drm_dp_link_power_up(struct drm_dp_aux *aux, unsignedchar revision)
{
u8 value; int err;
/* DP_SET_POWER register is only available on DPCD v1.1 and later */ if (revision < DP_DPCD_REV_11) return 0;
/* * According to the DP 1.1 specification, a "Sink Device must exit the * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink * Control Field" (register 0x600).
*/
usleep_range(1000, 2000);
return 0;
}
EXPORT_SYMBOL(drm_dp_link_power_up);
/** * drm_dp_link_power_down() - power down a DisplayPort link * @aux: DisplayPort AUX channel * @revision: DPCD revision supported on the link * * Returns 0 on success or a negative error code on failure.
*/ int drm_dp_link_power_down(struct drm_dp_aux *aux, unsignedchar revision)
{
u8 value; int err;
/* DP_SET_POWER register is only available on DPCD v1.1 and later */ if (revision < DP_DPCD_REV_11) return 0;
ret = drm_dp_dpcd_write_data(aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); if (ret < 0) {
drm_dbg_kms(aux->drm_dev, "failed to write payload allocation %d\n", ret); goto fail;
}
retry:
ret = drm_dp_dpcd_read_byte(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); if (ret < 0) {
drm_dbg_kms(aux->drm_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(aux->drm_dev, "status not set after read payload table status %d\n",
status);
ret = -EINVAL; goto fail;
}
ret = 0;
fail: return ret;
}
EXPORT_SYMBOL(drm_dp_dpcd_write_payload);
/** * drm_dp_dpcd_clear_payload() - Clear the entire VC Payload ID table * @aux: DisplayPort AUX channel * * Clear the entire VC Payload ID table. * * Returns: 0 on success, negative error code on errors.
*/ int drm_dp_dpcd_clear_payload(struct drm_dp_aux *aux)
{ return drm_dp_dpcd_write_payload(aux, 0, 0, 0x3f);
}
EXPORT_SYMBOL(drm_dp_dpcd_clear_payload);
/** * drm_dp_dpcd_poll_act_handled() - Poll for ACT handled status * @aux: DisplayPort AUX channel * @timeout_ms: Timeout in ms * * Try waiting for the sink to finish updating its payload table by polling for * the ACT handled bit of DP_PAYLOAD_TABLE_UPDATE_STATUS for up to @timeout_ms * milliseconds, defaulting to 3000 ms if 0. * * Returns: * 0 if the ACT was handled in time, negative error code on failure.
*/ int drm_dp_dpcd_poll_act_handled(struct drm_dp_aux *aux, int timeout_ms)
{ int ret, status;
/* default to 3 seconds, this is arbitrary */
timeout_ms = timeout_ms ?: 3000;
ret = readx_poll_timeout(read_payload_update_status, aux, status,
status & DP_PAYLOAD_ACT_HANDLED || status < 0,
200, timeout_ms * USEC_PER_MSEC); if (ret < 0 && status >= 0) {
drm_err(aux->drm_dev, "Failed to get ACT after %d ms, last status: %02x\n",
timeout_ms, status); return -EINVAL;
} elseif (status < 0) { /* * Failure here isn't unexpected - the hub may have * just been unplugged
*/
drm_dbg_kms(aux->drm_dev, "Failed to read payload table status: %d\n", status); return status;
}
/** * drm_dp_downstream_is_type() - is the downstream facing port of certain type? * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * @type: port type to be checked. Can be: * %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI, * %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID, * %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS. * * Caveat: Only works with DPCD 1.1+ port caps. * * Returns: whether the downstream facing port matches the type.
*/ bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], u8 type)
{ return drm_dp_is_branch(dpcd) &&
dpcd[DP_DPCD_REV] >= 0x11 &&
(port_cap[0] & DP_DS_PORT_TYPE_MASK) == type;
}
EXPORT_SYMBOL(drm_dp_downstream_is_type);
/** * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS? * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * @drm_edid: EDID * * Returns: whether the downstream facing port is TMDS (HDMI/DVI).
*/ bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], conststruct drm_edid *drm_edid)
{ if (dpcd[DP_DPCD_REV] < 0x11) { switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { case DP_DWN_STRM_PORT_TYPE_TMDS: returntrue; default: returnfalse;
}
}
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_DP_DUALMODE: if (is_edid_digital_input_dp(drm_edid)) returnfalse;
fallthrough; case DP_DS_PORT_TYPE_DVI: case DP_DS_PORT_TYPE_HDMI: returntrue; default: returnfalse;
}
}
EXPORT_SYMBOL(drm_dp_downstream_is_tmds);
/** * drm_dp_send_real_edid_checksum() - send back real edid checksum value * @aux: DisplayPort AUX channel * @real_edid_checksum: real edid checksum for the last block * * Returns: * True on success
*/ bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux,
u8 real_edid_checksum)
{
u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0;
if (!auto_test_req || !link_edid_read) {
drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n",
aux->name); returnfalse;
}
if (drm_dp_dpcd_write_byte(aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
auto_test_req) < 0) {
drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n",
aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); returnfalse;
}
/* send back checksum for the last edid extension block data */ if (drm_dp_dpcd_write_byte(aux, DP_TEST_EDID_CHECKSUM,
real_edid_checksum) < 0) {
drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n",
aux->name, DP_TEST_EDID_CHECKSUM); returnfalse;
}
/* * Prior to DP1.3 the bit represented by * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. * If it is set DP_DPCD_REV at 0000h could be at a value less than * the true capability of the panel. The only way to check is to * then compare 0000h and 2200h.
*/ if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) return 0;
ret = drm_dp_dpcd_read_data(aux, DP_DP13_DPCD_REV, &dpcd_ext, sizeof(dpcd_ext)); if (ret < 0) return ret;
if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) {
drm_dbg_kms(aux->drm_dev, "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n",
aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); return 0;
}
if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext))) return 0;
drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd);
memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext));
return 0;
}
/** * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if * available * @aux: DisplayPort AUX channel * @dpcd: Buffer to store the resulting DPCD in * * Attempts to read the base DPCD caps for @aux. Additionally, this function * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if * present. * * Returns: %0 if the DPCD was read successfully, negative error code * otherwise.
*/ int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux,
u8 dpcd[DP_RECEIVER_CAP_SIZE])
{ int ret;
ret = drm_dp_dpcd_read_data(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE); if (ret < 0) return ret; if (dpcd[DP_DPCD_REV] == 0) return -EIO;
ret = drm_dp_read_extended_dpcd_caps(aux, dpcd); if (ret < 0) return ret;
/** * drm_dp_read_downstream_info() - read DPCD downstream port info if available * @aux: DisplayPort AUX channel * @dpcd: A cached copy of the port's DPCD * @downstream_ports: buffer to store the downstream port info in * * See also: * drm_dp_downstream_max_clock() * drm_dp_downstream_max_bpc() * * Returns: 0 if either the downstream port info was read successfully or * there was no downstream info to read, or a negative error code otherwise.
*/ 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])
{ int ret;
u8 len;
/* No downstream info to read */ if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) return 0;
/* Some branches advertise having 0 downstream ports, despite also advertising they have a * downstream port present. The DP spec isn't clear on if this is allowed or not, but since * some branches do it we need to handle it regardless.
*/
len = drm_dp_downstream_port_count(dpcd); if (!len) return 0;
if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE)
len *= 4;
ret = drm_dp_dpcd_read_data(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len); if (ret < 0) return ret;
/** * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * * Returns: Downstream facing port max dot clock in kHz on success, * or 0 if max clock not defined
*/ int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4])
{ if (!drm_dp_is_branch(dpcd)) return 0;
/** * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * @drm_edid: EDID * * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success, * or 0 if max TMDS clock not defined
*/ int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], conststruct drm_edid *drm_edid)
{ if (!drm_dp_is_branch(dpcd)) return 0;
if (dpcd[DP_DPCD_REV] < 0x11) { switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { case DP_DWN_STRM_PORT_TYPE_TMDS: return 165000; default: return 0;
}
}
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_DP_DUALMODE: if (is_edid_digital_input_dp(drm_edid)) return 0; /* * It's left up to the driver to check the * DP dual mode adapter's max TMDS clock. * * Unfortunately it looks like branch devices * may not fordward that the DP dual mode i2c * access so we just usually get i2c nak :(
*/
fallthrough; case DP_DS_PORT_TYPE_HDMI: /* * We should perhaps assume 165 MHz when detailed cap * info is not available. But looks like many typical * branch devices fall into that category and so we'd * probably end up with users complaining that they can't * get high resolution modes with their favorite dongle. * * So let's limit to 300 MHz instead since DPCD 1.4 * HDMI 2.0 DFPs are required to have the detailed cap * info. So it's more likely we're dealing with a HDMI 1.4 * compatible* device here.
*/ if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) return 300000; return port_cap[1] * 2500; case DP_DS_PORT_TYPE_DVI: if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) return 165000; /* FIXME what to do about DVI dual link? */ return port_cap[1] * 2500; default: return 0;
}
}
EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock);
/** * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * @drm_edid: EDID * * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success, * or 0 if max TMDS clock not defined
*/ int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], conststruct drm_edid *drm_edid)
{ if (!drm_dp_is_branch(dpcd)) return 0;
if (dpcd[DP_DPCD_REV] < 0x11) { switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { case DP_DWN_STRM_PORT_TYPE_TMDS: return 25000; default: return 0;
}
}
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_DP_DUALMODE: if (is_edid_digital_input_dp(drm_edid)) return 0;
fallthrough; case DP_DS_PORT_TYPE_DVI: case DP_DS_PORT_TYPE_HDMI: /* * Unclear whether the protocol converter could * utilize pixel replication. Assume it won't.
*/ return 25000; default: return 0;
}
}
EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock);
/** * drm_dp_downstream_max_bpc() - extract downstream facing port max * bits per component * @dpcd: DisplayPort configuration data * @port_cap: downstream facing port capabilities * @drm_edid: EDID * * Returns: Max bpc on success or 0 if max bpc not defined
*/ int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], conststruct drm_edid *drm_edid)
{ if (!drm_dp_is_branch(dpcd)) return 0;
if (dpcd[DP_DPCD_REV] < 0x11) { switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { case DP_DWN_STRM_PORT_TYPE_DP: return 0; default: return 8;
}
}
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_DP: return 0; case DP_DS_PORT_TYPE_DP_DUALMODE: if (is_edid_digital_input_dp(drm_edid)) return 0;
fallthrough; case DP_DS_PORT_TYPE_HDMI: case DP_DS_PORT_TYPE_DVI: case DP_DS_PORT_TYPE_VGA: if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) return 8;
switch (port_cap[2] & DP_DS_MAX_BPC_MASK) { case DP_DS_8BPC: return 8; case DP_DS_10BPC: return 10; case DP_DS_12BPC: return 12; case DP_DS_16BPC: return 16; default: return 8;
} break; default: return 8;
}
}
EXPORT_SYMBOL(drm_dp_downstream_max_bpc);
/** * drm_dp_downstream_420_passthrough() - determine downstream facing port * YCbCr 4:2:0 pass-through capability * @dpcd: DisplayPort configuration data * @port_cap: downstream facing port capabilities * * Returns: whether the downstream facing port can pass through YCbCr 4:2:0
*/ bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4])
{ if (!drm_dp_is_branch(dpcd)) returnfalse;
if (dpcd[DP_DPCD_REV] < 0x13) returnfalse;
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_DP: returntrue; case DP_DS_PORT_TYPE_HDMI: if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) returnfalse;
/** * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port * RGB->YCbCr conversion capability * @dpcd: DisplayPort configuration data * @port_cap: downstream facing port capabilities * @color_spc: Colorspace for which conversion cap is sought * * Returns: whether the downstream facing port can convert RGB->YCbCr for a given * colorspace.
*/ bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4],
u8 color_spc)
{ if (!drm_dp_is_branch(dpcd)) returnfalse;
if (dpcd[DP_DPCD_REV] < 0x13) returnfalse;
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_HDMI: if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) returnfalse;
/** * drm_dp_downstream_mode() - return a mode for downstream facing port * @dev: DRM device * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * * Provides a suitable mode for downstream facing ports without EDID. * * Returns: A new drm_display_mode on success or NULL on failure
*/ struct drm_display_mode *
drm_dp_downstream_mode(struct drm_device *dev, const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4])
{
u8 vic;
if (!drm_dp_is_branch(dpcd)) return NULL;
if (dpcd[DP_DPCD_REV] < 0x11) return NULL;
switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { case DP_DS_PORT_TYPE_NON_EDID: switch (port_cap[0] & DP_DS_NON_EDID_MASK) { case DP_DS_NON_EDID_720x480i_60:
vic = 6; break; case DP_DS_NON_EDID_720x480i_50:
vic = 21; break; case DP_DS_NON_EDID_1920x1080i_60:
vic = 5; break; case DP_DS_NON_EDID_1920x1080i_50:
vic = 20; break; case DP_DS_NON_EDID_1280x720_60:
vic = 4; break; case DP_DS_NON_EDID_1280x720_50:
vic = 19; break; default: return NULL;
} return drm_display_mode_from_cea_vic(dev, vic); default: return NULL;
}
}
EXPORT_SYMBOL(drm_dp_downstream_mode);
/** * drm_dp_downstream_id() - identify branch device * @aux: DisplayPort AUX channel * @id: DisplayPort branch device id * * Returns branch device id on success or NULL on failure
*/ int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6])
{ return drm_dp_dpcd_read_data(aux, DP_BRANCH_ID, id, 6);
}
EXPORT_SYMBOL(drm_dp_downstream_id);
/** * drm_dp_downstream_debug() - debug DP branch devices * @m: pointer for debugfs file * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * @drm_edid: EDID * @aux: DisplayPort AUX channel *
*/ void drm_dp_downstream_debug(struct seq_file *m, const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4], conststruct drm_edid *drm_edid, struct drm_dp_aux *aux)
{ bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] &
DP_DETAILED_CAP_INFO_AVAILABLE; int clk; int bpc; char id[7]; int len;
uint8_t rev[2]; int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; bool branch_device = drm_dp_is_branch(dpcd);
/** * drm_dp_subconnector_type() - get DP branch device type * @dpcd: DisplayPort configuration data * @port_cap: port capabilities
*/ enum drm_mode_subconnector
drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], const u8 port_cap[4])
{ int type; if (!drm_dp_is_branch(dpcd)) return DRM_MODE_SUBCONNECTOR_Native; /* DP 1.0 approach */ if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) {
type = dpcd[DP_DOWNSTREAMPORT_PRESENT] &
DP_DWN_STRM_PORT_TYPE_MASK;
switch (type) { case DP_DWN_STRM_PORT_TYPE_TMDS: /* Can be HDMI or DVI-D, DVI-D is a safer option */ return DRM_MODE_SUBCONNECTOR_DVID; case DP_DWN_STRM_PORT_TYPE_ANALOG: /* Can be VGA or DVI-A, VGA is more popular */ return DRM_MODE_SUBCONNECTOR_VGA; case DP_DWN_STRM_PORT_TYPE_DP: return DRM_MODE_SUBCONNECTOR_DisplayPort; case DP_DWN_STRM_PORT_TYPE_OTHER: default: return DRM_MODE_SUBCONNECTOR_Unknown;
}
}
type = port_cap[0] & DP_DS_PORT_TYPE_MASK;
switch (type) { case DP_DS_PORT_TYPE_DP: case DP_DS_PORT_TYPE_DP_DUALMODE: return DRM_MODE_SUBCONNECTOR_DisplayPort; case DP_DS_PORT_TYPE_VGA: return DRM_MODE_SUBCONNECTOR_VGA; case DP_DS_PORT_TYPE_DVI: return DRM_MODE_SUBCONNECTOR_DVID; case DP_DS_PORT_TYPE_HDMI: return DRM_MODE_SUBCONNECTOR_HDMIA; case DP_DS_PORT_TYPE_WIRELESS: return DRM_MODE_SUBCONNECTOR_Wireless; case DP_DS_PORT_TYPE_NON_EDID: default: return DRM_MODE_SUBCONNECTOR_Unknown;
}
}
EXPORT_SYMBOL(drm_dp_subconnector_type);
/** * drm_dp_set_subconnector_property - set subconnector for DP connector * @connector: connector to set property on * @status: connector status * @dpcd: DisplayPort configuration data * @port_cap: port capabilities * * Called by a driver on every detect event.
*/ void drm_dp_set_subconnector_property(struct drm_connector *connector, enum drm_connector_status status, const u8 *dpcd, const u8 port_cap[4])
{ enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown;
/** * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink * count * @connector: The DRM connector to check * @dpcd: A cached copy of the connector's DPCD RX capabilities * @desc: A cached copy of the connector's DP descriptor * * See also: drm_dp_read_sink_count() * * Returns: %True if the (e)DP connector has a valid sink count that should * be probed, %false otherwise.
*/ bool drm_dp_read_sink_count_cap(struct drm_connector *connector, const u8 dpcd[DP_RECEIVER_CAP_SIZE], conststruct drm_dp_desc *desc)
{ /* Some eDP panels don't set a valid value for the sink count */ return connector->connector_type != DRM_MODE_CONNECTOR_eDP &&
dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 &&
dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT &&
!drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT);
}
EXPORT_SYMBOL(drm_dp_read_sink_count_cap);
/** * drm_dp_read_sink_count() - Retrieve the sink count for a given sink * @aux: The DP AUX channel to use * * See also: drm_dp_read_sink_count_cap() * * Returns: The current sink count reported by @aux, or a negative error code * otherwise.
*/ int drm_dp_read_sink_count(struct drm_dp_aux *aux)
{
u8 count; int ret;
ret = drm_dp_dpcd_read_byte(aux, DP_SINK_COUNT, &count); if (ret < 0) return ret;
staticvoid drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg)
{ /* * In case of i2c defer or short i2c ack reply to a write, * we need to switch to WRITE_STATUS_UPDATE to drain the * rest of the message
*/ if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) {
msg->request &= DP_AUX_I2C_MOT;
msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE;
}
}
/* * Calculate the duration of the AUX request/reply in usec. Gives the * "best" case estimate, ie. successful while as short as possible.
*/ staticint drm_dp_aux_req_duration(conststruct drm_dp_aux_msg *msg)
{ int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN +
AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN;
if ((msg->request & DP_AUX_I2C_READ) == 0)
len += msg->size * 8;
return len;
}
staticint drm_dp_aux_reply_duration(conststruct drm_dp_aux_msg *msg)
{ int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN +
AUX_CMD_LEN + AUX_REPLY_PAD_LEN;
/* * For read we expect what was asked. For writes there will * be 0 or 1 data bytes. Assume 0 for the "best" case.
*/ if (msg->request & DP_AUX_I2C_READ)
len += msg->size * 8;
/* * Calculate the length of the i2c transfer in usec, assuming * the i2c bus speed is as specified. Gives the "worst" * case estimate, ie. successful while as long as possible. * Doesn't account the "MOT" bit, and instead assumes each * message includes a START, ADDRESS and STOP. Neither does it * account for additional random variables such as clock stretching.
*/ staticint drm_dp_i2c_msg_duration(conststruct drm_dp_aux_msg *msg, int i2c_speed_khz)
{ /* AUX bitrate is 1MHz, i2c bitrate as specified */ return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN +
msg->size * I2C_DATA_LEN +
I2C_STOP_LEN) * 1000, i2c_speed_khz);
}
/* * Determine how many retries should be attempted to successfully transfer * the specified message, based on the estimated durations of the * i2c and AUX transfers.
*/ staticint drm_dp_i2c_retry_count(conststruct drm_dp_aux_msg *msg, int i2c_speed_khz)
{ int aux_time_us = drm_dp_aux_req_duration(msg) +
drm_dp_aux_reply_duration(msg); int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz);
/* * FIXME currently assumes 10 kHz as some real world devices seem * to require it. We should query/set the speed via DPCD if supported.
*/ staticint dp_aux_i2c_speed_khz __read_mostly = 10;
module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644);
MODULE_PARM_DESC(dp_aux_i2c_speed_khz, "Assumed speed of the i2c bus in kHz, (1-400, default 10)");
/* * Transfer a single I2C-over-AUX message and handle various error conditions, * retrying the transaction as appropriate. It is assumed that the * &drm_dp_aux.transfer function does not modify anything in the msg other than the * reply field. * * Returns bytes transferred on success, or a negative error code on failure.
*/ staticint drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{ unsignedint retry, defer_i2c; int ret; /* * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device * is required to retry at least seven times upon receiving AUX_DEFER * before giving up the AUX transaction. * * We also try to account for the i2c bus speed.
*/ int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz));
for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) {
ret = aux->transfer(aux, msg); if (ret < 0) { if (ret == -EBUSY) continue;
/* * While timeouts can be errors, they're usually normal * behavior (for instance, when a driver tries to * communicate with a non-existent DisplayPort device). * Avoid spamming the kernel log with timeout errors.
*/ if (ret == -ETIMEDOUT)
drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n",
aux->name); else
drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n",
aux->name, ret); return ret;
}
switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) { case DP_AUX_NATIVE_REPLY_ACK: /* * For I2C-over-AUX transactions this isn't enough, we * need to check for the I2C ACK reply.
*/ break;
case DP_AUX_NATIVE_REPLY_DEFER:
drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name); /* * We could check for I2C bit rate capabilities and if * available adjust this interval. We could also be * more careful with DP-to-legacy adapters where a * long legacy cable may force very low I2C bit rates. * * For now just defer for long enough to hopefully be * safe for all use-cases.
*/
usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); continue;
switch (msg->reply & DP_AUX_I2C_REPLY_MASK) { case DP_AUX_I2C_REPLY_ACK: /* * Both native ACK and I2C ACK replies received. We * can assume the transfer was successful.
*/ if (ret != msg->size)
drm_dp_i2c_msg_write_status_update(msg); return ret;
case DP_AUX_I2C_REPLY_DEFER:
drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name); /* DP Compliance Test 4.2.2.5 Requirement: * Must have at least 7 retries for I2C defers on the * transaction to pass this test
*/
aux->i2c_defer_count++; if (defer_i2c < 7)
defer_i2c++;
usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100);
drm_dp_i2c_msg_write_status_update(msg);
/* * Keep retrying drm_dp_i2c_do_msg until all data has been transferred. * * Returns an error code on failure, or a recommended transfer size on success.
*/ staticint drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg)
{ int err, ret = orig_msg->size; struct drm_dp_aux_msg msg = *orig_msg;
/* * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX * packets to be as large as possible. If not, the I2C transactions never * succeed. Hence the default is maximum.
*/ staticint dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES;
module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644);
MODULE_PARM_DESC(dp_aux_i2c_transfer_size, "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)");
staticint drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
{ struct drm_dp_aux *aux = adapter->algo_data; unsignedint i, j; unsigned transfer_size; struct drm_dp_aux_msg msg; int err = 0;
for (i = 0; i < num; i++) {
msg.address = msgs[i].addr;
if (!aux->no_zero_sized) {
drm_dp_i2c_msg_set_request(&msg, &msgs[i]); /* Send a bare address packet to start the transaction. * Zero sized messages specify an address only (bare * address) transaction.
*/
msg.buffer = NULL;
msg.size = 0;
err = drm_dp_i2c_do_msg(aux, &msg);
}
/* * Reset msg.request in case in case it got * changed into a WRITE_STATUS_UPDATE.
*/
drm_dp_i2c_msg_set_request(&msg, &msgs[i]);
if (err < 0) break; /* We want each transaction to be as large as possible, but * we'll go to smaller sizes if the hardware gives us a * short reply.
*/
transfer_size = dp_aux_i2c_transfer_size; for (j = 0; j < msgs[i].len; j += msg.size) {
msg.buffer = msgs[i].buf + j;
msg.size = min(transfer_size, msgs[i].len - j);
/* * Reset msg.request in case in case it got * changed into a WRITE_STATUS_UPDATE.
*/
drm_dp_i2c_msg_set_request(&msg, &msgs[i]);
if (err < 0) break;
transfer_size = err;
} if (err < 0) break;
} if (err >= 0)
err = num;
if (!aux->no_zero_sized) { /* Send a bare address packet to close out the transaction. * Zero sized messages specify an address only (bare * address) transaction.
*/
msg.request &= ~DP_AUX_I2C_MOT;
msg.buffer = NULL;
msg.size = 0;
(void)drm_dp_i2c_do_msg(aux, &msg);
} return err;
}
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.