diff options
Diffstat (limited to 'drivers/gpu/drm/display/drm_dp_helper.c')
-rw-r--r-- | drivers/gpu/drm/display/drm_dp_helper.c | 421 |
1 files changed, 398 insertions, 23 deletions
diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c index b1ca3a1100da..61c7c2c588c6 100644 --- a/drivers/gpu/drm/display/drm_dp_helper.c +++ b/drivers/gpu/drm/display/drm_dp_helper.c @@ -22,19 +22,21 @@ #include <linux/backlight.h> #include <linux/delay.h> +#include <linux/dynamic_debug.h> #include <linux/errno.h> #include <linux/i2c.h> #include <linux/init.h> +#include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/seq_file.h> #include <linux/string_helpers.h> -#include <linux/dynamic_debug.h> #include <drm/display/drm_dp_helper.h> #include <drm/display/drm_dp_mst_helper.h> #include <drm/drm_edid.h> +#include <drm/drm_fixed.h> #include <drm/drm_print.h> #include <drm/drm_vblank.h> #include <drm/drm_panel.h> @@ -533,6 +535,15 @@ static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request, mutex_lock(&aux->hw_mutex); /* + * 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 @@ -600,6 +611,29 @@ int drm_dp_dpcd_probe(struct drm_dp_aux *aux, unsigned int offset) 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; + + mutex_lock(&aux->hw_mutex); + aux->powered_down = !powered; + mutex_unlock(&aux->hw_mutex); +} +EXPORT_SYMBOL(drm_dp_dpcd_set_powered); + +/** * 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 @@ -746,6 +780,128 @@ int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, } EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status); +static int read_payload_update_status(struct drm_dp_aux *aux) +{ + int ret; + u8 status; + + ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) + return ret; + + return status; +} + +/** + * drm_dp_dpcd_write_payload() - Write Virtual Channel information to payload table + * @aux: DisplayPort AUX channel + * @vcpid: Virtual Channel Payload ID + * @start_time_slot: Starting time slot + * @time_slot_count: Time slot count + * + * Write the Virtual Channel payload allocation table, checking the payload + * update status and retrying as necessary. + * + * Returns: + * 0 on success, negative error otherwise + */ +int drm_dp_dpcd_write_payload(struct drm_dp_aux *aux, + int vcpid, u8 start_time_slot, u8 time_slot_count) +{ + u8 payload_alloc[3], status; + int ret; + int retries = 0; + + drm_dp_dpcd_writeb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, + DP_PAYLOAD_TABLE_UPDATED); + + payload_alloc[0] = vcpid; + payload_alloc[1] = start_time_slot; + payload_alloc[2] = time_slot_count; + + ret = drm_dp_dpcd_write(aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); + if (ret != 3) { + drm_dbg_kms(aux->drm_dev, "failed to write payload allocation %d\n", ret); + goto fail; + } + +retry: + ret = drm_dp_dpcd_readb(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; + } else if (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; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dpcd_poll_act_handled); + static bool is_edid_digital_input_dp(const struct drm_edid *drm_edid) { /* FIXME: get rid of drm_edid_raw() */ @@ -1858,6 +2014,9 @@ static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, struct drm_dp_aux_msg msg; int err = 0; + if (aux->powered_down) + return -EBUSY; + dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES); memset(&msg, 0, sizeof(msg)); @@ -2078,7 +2237,7 @@ EXPORT_SYMBOL(drm_dp_aux_init); * drm_dp_aux_register() in &drm_connector_funcs.late_register, and likewise to * call drm_dp_aux_unregister() in &drm_connector_funcs.early_unregister. * Functions which don't follow this will likely Oops when - * %CONFIG_DRM_DP_AUX_CHARDEV is enabled. + * %CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV is enabled. * * For devices where the AUX channel is a device that exists independently of * the &drm_device that uses it, such as SoCs and bridge devices, it is @@ -2246,6 +2405,8 @@ static const struct dpcd_quirk dpcd_quirk_list[] = { { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) }, /* Synaptics DP1.4 MST hubs require DSC for some modes on which it applies HBLANK expansion. */ { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_HBLANK_EXPANSION_REQUIRES_DSC) }, + /* MediaTek panels (at least in U3224KBA) require DSC for modes with a short HBLANK on UHBR links. */ + { OUI(0x00, 0x0C, 0xE7), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_HBLANK_EXPANSION_REQUIRES_DSC) }, /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */ { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) }, }; @@ -2290,6 +2451,31 @@ drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch) #undef DEVICE_ID_ANY #undef DEVICE_ID +static int drm_dp_read_ident(struct drm_dp_aux *aux, unsigned int offset, + struct drm_dp_dpcd_ident *ident) +{ + int ret; + + ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); + + return ret < 0 ? ret : 0; +} + +static void drm_dp_dump_desc(struct drm_dp_aux *aux, + const char *device_name, const struct drm_dp_desc *desc) +{ + const struct drm_dp_dpcd_ident *ident = &desc->ident; + + drm_dbg_kms(aux->drm_dev, + "%s: %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", + aux->name, device_name, + (int)sizeof(ident->oui), ident->oui, + (int)strnlen(ident->device_id, sizeof(ident->device_id)), ident->device_id, + ident->hw_rev >> 4, ident->hw_rev & 0xf, + ident->sw_major_rev, ident->sw_minor_rev, + desc->quirks); +} + /** * drm_dp_read_desc - read sink/branch descriptor from DPCD * @aux: DisplayPort AUX channel @@ -2306,28 +2492,49 @@ int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, { struct drm_dp_dpcd_ident *ident = &desc->ident; unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI; - int ret, dev_id_len; + int ret; - ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); + ret = drm_dp_read_ident(aux, offset, ident); if (ret < 0) return ret; desc->quirks = drm_dp_get_quirks(ident, is_branch); - dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id)); - - drm_dbg_kms(aux->drm_dev, - "%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", - aux->name, is_branch ? "branch" : "sink", - (int)sizeof(ident->oui), ident->oui, dev_id_len, - ident->device_id, ident->hw_rev >> 4, ident->hw_rev & 0xf, - ident->sw_major_rev, ident->sw_minor_rev, desc->quirks); + drm_dp_dump_desc(aux, is_branch ? "DP branch" : "DP sink", desc); return 0; } EXPORT_SYMBOL(drm_dp_read_desc); /** + * drm_dp_dump_lttpr_desc - read and dump the DPCD descriptor for an LTTPR PHY + * @aux: DisplayPort AUX channel + * @dp_phy: LTTPR PHY instance + * + * Read the DPCD LTTPR PHY descriptor for @dp_phy and print a debug message + * with its details to dmesg. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_dump_lttpr_desc(struct drm_dp_aux *aux, enum drm_dp_phy dp_phy) +{ + struct drm_dp_desc desc = {}; + int ret; + + if (drm_WARN_ON(aux->drm_dev, dp_phy < DP_PHY_LTTPR1 || dp_phy > DP_MAX_LTTPR_COUNT)) + return -EINVAL; + + ret = drm_dp_read_ident(aux, DP_OUI_PHY_REPEATER(dp_phy), &desc.ident); + if (ret < 0) + return ret; + + drm_dp_dump_desc(aux, drm_dp_phy_name(dp_phy), &desc); + + return 0; +} +EXPORT_SYMBOL(drm_dp_dump_lttpr_desc); + +/** * drm_dp_dsc_sink_bpp_incr() - Get bits per pixel increment * @dsc_dpcd: DSC capabilities from DPCD * @@ -2337,7 +2544,7 @@ u8 drm_dp_dsc_sink_bpp_incr(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) { u8 bpp_increment_dpcd = dsc_dpcd[DP_DSC_BITS_PER_PIXEL_INC - DP_DSC_SUPPORT]; - switch (bpp_increment_dpcd) { + switch (bpp_increment_dpcd & DP_DSC_BITS_PER_PIXEL_MASK) { case DP_DSC_BITS_PER_PIXEL_1_16: return 16; case DP_DSC_BITS_PER_PIXEL_1_8: @@ -2897,25 +3104,156 @@ static const char *dp_content_type_get_name(enum dp_content_type content_type) } } -void drm_dp_vsc_sdp_log(const char *level, struct device *dev, - const struct drm_dp_vsc_sdp *vsc) +void drm_dp_vsc_sdp_log(struct drm_printer *p, const struct drm_dp_vsc_sdp *vsc) { -#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__) - DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC", + drm_printf(p, "DP SDP: VSC, revision %u, length %u\n", vsc->revision, vsc->length); - DP_SDP_LOG(" pixelformat: %s\n", + drm_printf(p, " pixelformat: %s\n", dp_pixelformat_get_name(vsc->pixelformat)); - DP_SDP_LOG(" colorimetry: %s\n", + drm_printf(p, " colorimetry: %s\n", dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry)); - DP_SDP_LOG(" bpc: %u\n", vsc->bpc); - DP_SDP_LOG(" dynamic range: %s\n", + drm_printf(p, " bpc: %u\n", vsc->bpc); + drm_printf(p, " dynamic range: %s\n", dp_dynamic_range_get_name(vsc->dynamic_range)); - DP_SDP_LOG(" content type: %s\n", + drm_printf(p, " content type: %s\n", dp_content_type_get_name(vsc->content_type)); -#undef DP_SDP_LOG } EXPORT_SYMBOL(drm_dp_vsc_sdp_log); +void drm_dp_as_sdp_log(struct drm_printer *p, const struct drm_dp_as_sdp *as_sdp) +{ + drm_printf(p, "DP SDP: AS_SDP, revision %u, length %u\n", + as_sdp->revision, as_sdp->length); + drm_printf(p, " vtotal: %d\n", as_sdp->vtotal); + drm_printf(p, " target_rr: %d\n", as_sdp->target_rr); + drm_printf(p, " duration_incr_ms: %d\n", as_sdp->duration_incr_ms); + drm_printf(p, " duration_decr_ms: %d\n", as_sdp->duration_decr_ms); + drm_printf(p, " operation_mode: %d\n", as_sdp->mode); +} +EXPORT_SYMBOL(drm_dp_as_sdp_log); + +/** + * drm_dp_as_sdp_supported() - check if adaptive sync sdp is supported + * @aux: DisplayPort AUX channel + * @dpcd: DisplayPort configuration data + * + * Returns true if adaptive sync sdp is supported, else returns false + */ +bool drm_dp_as_sdp_supported(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 rx_feature; + + if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_13) + return false; + + if (drm_dp_dpcd_readb(aux, DP_DPRX_FEATURE_ENUMERATION_LIST_CONT_1, + &rx_feature) != 1) { + drm_dbg_dp(aux->drm_dev, + "Failed to read DP_DPRX_FEATURE_ENUMERATION_LIST_CONT_1\n"); + return false; + } + + return (rx_feature & DP_ADAPTIVE_SYNC_SDP_SUPPORTED); +} +EXPORT_SYMBOL(drm_dp_as_sdp_supported); + +/** + * drm_dp_vsc_sdp_supported() - check if vsc sdp is supported + * @aux: DisplayPort AUX channel + * @dpcd: DisplayPort configuration data + * + * Returns true if vsc sdp is supported, else returns false + */ +bool drm_dp_vsc_sdp_supported(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 rx_feature; + + if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_13) + return false; + + if (drm_dp_dpcd_readb(aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &rx_feature) != 1) { + drm_dbg_dp(aux->drm_dev, "failed to read DP_DPRX_FEATURE_ENUMERATION_LIST\n"); + return false; + } + + return (rx_feature & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); +} +EXPORT_SYMBOL(drm_dp_vsc_sdp_supported); + +/** + * drm_dp_vsc_sdp_pack() - pack a given vsc sdp into generic dp_sdp + * @vsc: vsc sdp initialized according to its purpose as defined in + * table 2-118 - table 2-120 in DP 1.4a specification + * @sdp: valid handle to the generic dp_sdp which will be packed + * + * Returns length of sdp on success and error code on failure + */ +ssize_t drm_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, + struct dp_sdp *sdp) +{ + size_t length = sizeof(struct dp_sdp); + + memset(sdp, 0, sizeof(struct dp_sdp)); + + /* + * Prepare VSC Header for SU as per DP 1.4a spec, Table 2-119 + * VSC SDP Header Bytes + */ + sdp->sdp_header.HB0 = 0; /* Secondary-Data Packet ID = 0 */ + sdp->sdp_header.HB1 = vsc->sdp_type; /* Secondary-data Packet Type */ + sdp->sdp_header.HB2 = vsc->revision; /* Revision Number */ + sdp->sdp_header.HB3 = vsc->length; /* Number of Valid Data Bytes */ + + if (vsc->revision == 0x6) { + sdp->db[0] = 1; + sdp->db[3] = 1; + } + + /* + * Revision 0x5 and revision 0x7 supports Pixel Encoding/Colorimetry + * Format as per DP 1.4a spec and DP 2.0 respectively. + */ + if (!(vsc->revision == 0x5 || vsc->revision == 0x7)) + goto out; + + /* VSC SDP Payload for DB16 through DB18 */ + /* Pixel Encoding and Colorimetry Formats */ + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; /* DB16[7:4] */ + sdp->db[16] |= vsc->colorimetry & 0xf; /* DB16[3:0] */ + + switch (vsc->bpc) { + case 6: + /* 6bpc: 0x0 */ + break; + case 8: + sdp->db[17] = 0x1; /* DB17[3:0] */ + break; + case 10: + sdp->db[17] = 0x2; + break; + case 12: + sdp->db[17] = 0x3; + break; + case 16: + sdp->db[17] = 0x4; + break; + default: + WARN(1, "Missing case %d\n", vsc->bpc); + return -EINVAL; + } + + /* Dynamic Range and Component Bit Depth */ + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) + sdp->db[17] |= 0x80; /* DB17[7] */ + + /* Content Type */ + sdp->db[18] = vsc->content_type & 0x7; + +out: + return length; +} +EXPORT_SYMBOL(drm_dp_vsc_sdp_pack); + /** * drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON * @dpcd: DisplayPort configuration data @@ -3982,6 +4320,13 @@ int drm_dp_bw_overhead(int lane_count, int hactive, u32 overhead = 1000000; int symbol_cycles; + if (lane_count == 0 || hactive == 0 || bpp_x16 == 0) { + DRM_DEBUG_KMS("Invalid BW overhead params: lane_count %d, hactive %d, bpp_x16 " FXP_Q4_FMT "\n", + lane_count, hactive, + FXP_Q4_ARGS(bpp_x16)); + return 0; + } + /* * DP Standard v2.1 2.6.4.1 * SSC downspread and ref clock variation margin: @@ -4058,3 +4403,33 @@ int drm_dp_bw_channel_coding_efficiency(bool is_uhbr) return 800000; } EXPORT_SYMBOL(drm_dp_bw_channel_coding_efficiency); + +/** + * drm_dp_max_dprx_data_rate - Get the max data bandwidth of a DPRX sink + * @max_link_rate: max DPRX link rate in 10kbps units + * @max_lanes: max DPRX lane count + * + * Given a link rate and lanes, get the data bandwidth. + * + * Data bandwidth is the actual payload rate, which depends on the data + * bandwidth efficiency and the link rate. + * + * Note that protocol layers above the DPRX link level considered here can + * further limit the maximum data rate. Such layers are the MST topology (with + * limits on the link between the source and first branch device as well as on + * the whole MST path until the DPRX link) and (Thunderbolt) DP tunnels - + * which in turn can encapsulate an MST link with its own limit - with each + * SST or MST encapsulated tunnel sharing the BW of a tunnel group. + * + * Returns the maximum data rate in kBps units. + */ +int drm_dp_max_dprx_data_rate(int max_link_rate, int max_lanes) +{ + int ch_coding_efficiency = + drm_dp_bw_channel_coding_efficiency(drm_dp_is_uhbr_rate(max_link_rate)); + + return DIV_ROUND_DOWN_ULL(mul_u32_u32(max_link_rate * 10 * max_lanes, + ch_coding_efficiency), + 1000000 * 8); +} +EXPORT_SYMBOL(drm_dp_max_dprx_data_rate); |