/* * The display power domains used for TC ports depending on the * platform and TC mode (legacy, DP-alt, TBT): * * POWER_DOMAIN_DISPLAY_CORE: * -------------------------- * ADLP/all modes: * - TCSS/IOM access for PHY ready state. * ADLP+/all modes: * - DE/north-,south-HPD ISR access for HPD live state. * * POWER_DOMAIN_PORT_DDI_LANES_<port>: * ----------------------------------- * ICL+/all modes: * - DE/DDI_BUF access for port enabled state. * ADLP/all modes: * - DE/DDI_BUF access for PHY owned state. * * POWER_DOMAIN_AUX_USBC<TC port index>: * ------------------------------------- * ICL/legacy mode: * - TCSS/IOM,FIA access for PHY ready, owned and HPD live state * - TCSS/PHY: block TC-cold power state for using the PHY AUX and * main lanes. * ADLP/legacy, DP-alt modes: * - TCSS/PHY: block TC-cold power state for using the PHY AUX and * main lanes. * * POWER_DOMAIN_TC_COLD_OFF: * ------------------------- * ICL/DP-alt, TBT mode: * - TCSS/TBT: block TC-cold power state for using the (direct or * TBT DP-IN) AUX and main lanes. * * TGL/all modes: * - TCSS/IOM,FIA access for PHY ready, owned and HPD live state * - TCSS/PHY: block TC-cold power state for using the (direct or * TBT DP-IN) AUX and main lanes. * * ADLP/TBT mode: * - TCSS/TBT: block TC-cold power state for using the (TBT DP-IN) * AUX and main lanes. * * XELPDP+/all modes: * - TCSS/IOM,FIA access for PHY ready, owned state * - TCSS/PHY: block TC-cold power state for using the (direct or * TBT DP-IN) AUX and main lanes.
*/ bool intel_tc_cold_requires_aux_pw(struct intel_digital_port *dig_port)
{ struct intel_display *display = to_intel_display(dig_port); struct intel_tc_port *tc = to_tc_port(dig_port);
switch (lane_mask) { default:
MISSING_CASE(lane_mask);
fallthrough; case 0x1: case 0x2: case 0x4: case 0x8: return 1; case 0x3: case 0xc: return 2; case 0xf: return 4;
}
}
if (!(live_status_mask & ~valid_hpd_mask)) return;
/* If live status mismatches the VBT flag, trust the live status. */
drm_dbg_kms(display->drm, "Port %s: live status %08x mismatches the legacy port flag %08x, fixing flag\n",
tc->port_name, live_status_mask, valid_hpd_mask);
/* * Each Modular FIA instance houses 2 TC ports. In SOC that has more * than two TC ports, there are multiple instances of Modular FIA.
*/ if (modular_fia) {
tc->phy_fia = tc_port / 2;
tc->phy_fia_idx = tc_port % 2;
} else {
tc->phy_fia = FIA1;
tc->phy_fia_idx = tc_port;
}
}
if (fia_isr == 0xffffffff) {
drm_dbg_kms(display->drm, "Port %s: PHY in TCCOLD, nothing connected\n",
tc->port_name); return mask;
}
if (fia_isr & TC_LIVE_STATE_TBT(tc->phy_fia_idx))
mask |= BIT(TC_PORT_TBT_ALT); if (fia_isr & TC_LIVE_STATE_TC(tc->phy_fia_idx))
mask |= BIT(TC_PORT_DP_ALT);
if (pch_isr & isr_bit)
mask |= BIT(TC_PORT_LEGACY);
return mask;
}
/* * Return the PHY status complete flag indicating that display can acquire the * PHY ownership. The IOM firmware sets this flag when a DP-alt or legacy sink * is connected and it's ready to switch the ownership to display. The flag * will be left cleared when a TBT-alt sink is connected, where the PHY is * owned by the TBT subsystem and so switching the ownership to display is not * required.
*/ staticbool icl_tc_phy_is_ready(struct intel_tc_port *tc)
{ struct intel_display *display = to_intel_display(tc->dig_port);
u32 val;
assert_tc_cold_blocked(tc);
val = intel_de_read(display, PORT_TX_DFLEXDPPMS(tc->phy_fia)); if (val == 0xffffffff) {
drm_dbg_kms(display->drm, "Port %s: PHY in TCCOLD, assuming not ready\n",
tc->port_name); returnfalse;
}
return val & DP_PHY_MODE_STATUS_COMPLETED(tc->phy_fia_idx);
}
/* * This function implements the first part of the Connect Flow described by our * specification, Gen11 TypeC Programming chapter. The rest of the flow (reading * lanes, EDID, etc) is done as needed in the typical places. * * Unlike the other ports, type-C ports are not available to use as soon as we * get a hotplug. The type-C PHYs can be shared between multiple controllers: * display, USB, etc. As a result, handshaking through FIA is required around * connect and disconnect to cleanly transfer ownership with the controller and * set the type-C power state.
*/ staticbool tc_phy_verify_legacy_or_dp_alt_mode(struct intel_tc_port *tc, int required_lanes)
{ struct intel_display *display = to_intel_display(tc->dig_port); struct intel_digital_port *dig_port = tc->dig_port; int max_lanes;
/* * Now we have to re-check the live state, in case the port recently * became disconnected. Not necessary for legacy mode.
*/ if (!(tc_phy_hpd_live_status(tc) & BIT(TC_PORT_DP_ALT))) {
drm_dbg_kms(display->drm, "Port %s: PHY sudden disconnect\n",
tc->port_name); returnfalse;
}
/* * See the comment at the connect function. This implements the Disconnect * Flow.
*/ staticvoid icl_tc_phy_disconnect(struct intel_tc_port *tc)
{ switch (tc->mode) { case TC_PORT_LEGACY: case TC_PORT_DP_ALT:
icl_tc_phy_take_ownership(tc, false);
fallthrough; case TC_PORT_TBT_ALT:
tc_cold_unblock(tc, fetch_and_zero(&tc->lock_wakeref)); break; default:
MISSING_CASE(tc->mode);
}
}
if (cpu_isr & (cpu_isr_bits & GEN11_DE_TC_HOTPLUG_MASK))
mask |= BIT(TC_PORT_DP_ALT); if (cpu_isr & (cpu_isr_bits & GEN11_DE_TBT_HOTPLUG_MASK))
mask |= BIT(TC_PORT_TBT_ALT);
if (pch_isr & pch_isr_bit)
mask |= BIT(TC_PORT_LEGACY);
return mask;
}
/* * Return the PHY status complete flag indicating that display can acquire the * PHY ownership. The IOM firmware sets this flag when it's ready to switch * the ownership to display, regardless of what sink is connected (TBT-alt, * DP-alt, legacy or nothing). For TBT-alt sinks the PHY is owned by the TBT * subsystem and so switching the ownership to display is not required.
*/ staticbool adlp_tc_phy_is_ready(struct intel_tc_port *tc)
{ struct intel_display *display = to_intel_display(tc->dig_port); enum tc_port tc_port = intel_encoder_to_tc(&tc->dig_port->base);
u32 val;
assert_display_core_power_enabled(tc);
val = intel_de_read(display, TCSS_DDI_STATUS(tc_port)); if (val == 0xffffffff) {
drm_dbg_kms(display->drm, "Port %s: PHY in TCCOLD, assuming not ready\n",
tc->port_name); returnfalse;
}
return val & TCSS_DDI_STATUS_READY;
}
staticbool adlp_tc_phy_take_ownership(struct intel_tc_port *tc, bool take)
{ struct intel_display *display = to_intel_display(tc->dig_port); enum port port = tc->dig_port->base.port;
assert_tc_port_power_enabled(tc);
intel_de_rmw(display, DDI_BUF_CTL(port), DDI_BUF_CTL_TC_PHY_OWNERSHIP,
take ? DDI_BUF_CTL_TC_PHY_OWNERSHIP : 0);
returntrue;
}
staticbool adlp_tc_phy_is_owned(struct intel_tc_port *tc)
{ struct intel_display *display = to_intel_display(tc->dig_port); enum port port = tc->dig_port->base.port;
u32 val;
assert_tc_port_power_enabled(tc);
val = intel_de_read(display, DDI_BUF_CTL(port)); return val & DDI_BUF_CTL_TC_PHY_OWNERSHIP;
}
if (wait_for(xelpdp_tc_phy_tcss_power_is_enabled(tc) == enabled, 5)) {
drm_dbg_kms(display->drm, "Port %s: timeout waiting for TCSS power to get %s\n",
str_enabled_disabled(enabled),
tc->port_name); returnfalse;
}
returntrue;
}
/* * Gfx driver WA 14020908590 for PTL tcss_rxdetect_clkswb_req/ack * handshake violation when pwwreq= 0->1 during TC7/10 entry
*/ staticvoid xelpdp_tc_power_request_wa(struct intel_display *display, bool enable)
{ /* check if mailbox is running busy */ if (intel_de_wait_for_clear(display, TCSS_DISP_MAILBOX_IN_CMD,
TCSS_DISP_MAILBOX_IN_CMD_RUN_BUSY, 10)) {
drm_dbg_kms(display->drm, "Timeout waiting for TCSS mailbox run/busy bit to clear\n"); return;
}
/* wait to clear mailbox running busy bit before continuing */ if (intel_de_wait_for_clear(display, TCSS_DISP_MAILBOX_IN_CMD,
TCSS_DISP_MAILBOX_IN_CMD_RUN_BUSY, 10)) {
drm_dbg_kms(display->drm, "Timeout after writing data to mailbox. Mailbox run/busy bit did not clear\n"); return;
}
}
if (DISPLAY_VER(display) == 30)
xelpdp_tc_power_request_wa(display, enable);
val = intel_de_read(display, reg); if (enable)
val |= XELPDP_TCSS_POWER_REQUEST; else
val &= ~XELPDP_TCSS_POWER_REQUEST;
intel_de_write(display, reg, val);
}
val = intel_de_read(display, reg); if (take)
val |= XELPDP_TC_PHY_OWNERSHIP; else
val &= ~XELPDP_TC_PHY_OWNERSHIP;
intel_de_write(display, reg, val);
}
staticbool xelpdp_tc_phy_is_owned(struct intel_tc_port *tc)
{ struct intel_display *display = to_intel_display(tc->dig_port); enum port port = tc->dig_port->base.port;
i915_reg_t reg = XELPDP_PORT_BUF_CTL1(display, port);
read_pin_configuration(tc); /* * Set a valid lane count value for a DP-alt sink which got * disconnected. The driver can only disable the output on this PHY.
*/ if (tc->max_lane_count == 0)
tc->max_lane_count = 4;
}
/* Is the PHY owned by display i.e. is it in legacy or DP-alt mode? */ staticbool tc_phy_owned_by_display(struct intel_tc_port *tc, bool phy_is_ready, bool phy_is_owned)
{ struct intel_display *display = to_intel_display(tc->dig_port);
if (DISPLAY_VER(display) < 20) {
drm_WARN_ON(display->drm, phy_is_owned && !phy_is_ready);
/* * For legacy ports the IOM firmware initializes the PHY during boot-up * and system resume whether or not a sink is connected. Wait here for * the initialization to get ready.
*/ if (tc->legacy_port)
tc_phy_wait_for_ready(tc);
/** * intel_tc_port_init_mode: Read out HW state and init the given port's TypeC mode * @dig_port: digital port * * Read out the HW state and initialize the TypeC mode of @dig_port. The mode * will be locked until intel_tc_port_sanitize_mode() is called.
*/ void intel_tc_port_init_mode(struct intel_digital_port *dig_port)
{ struct intel_display *display = to_intel_display(dig_port); struct intel_tc_port *tc = to_tc_port(dig_port); bool update_mode = false;
tc_phy_get_hw_state(tc); /* * Save the initial mode for the state check in * intel_tc_port_sanitize_mode().
*/
tc->init_mode = tc->mode;
/* * The PHY needs to be connected for AUX to work during HW readout and * MST topology resume, but the PHY mode can only be changed if the * port is disabled. * * An exception is the case where BIOS leaves the PHY incorrectly * disconnected on an enabled legacy port. Work around that by * connecting the PHY even though the port is enabled. This doesn't * cause a problem as the PHY ownership state is ignored by the * IOM/TCSS firmware (only display can own the PHY in that case).
*/ if (!tc_port_is_enabled(tc)) {
update_mode = true;
} elseif (tc->mode == TC_PORT_DISCONNECTED) {
drm_WARN_ON(display->drm, !tc->legacy_port);
drm_err(display->drm, "Port %s: PHY disconnected on enabled port, connecting it\n",
tc->port_name);
update_mode = true;
}
if (update_mode)
intel_tc_port_update_mode(tc, 1, false);
/* Prevent changing tc->mode until intel_tc_port_sanitize_mode() is called. */
__intel_tc_port_get_link(tc);
if (dig_port->dp.is_mst) { /* TODO: get the PLL type for MST, once HW readout is done for it. */
active_streams = intel_dp_mst_active_streams(&dig_port->dp);
} elseif (crtc_state && crtc_state->hw.active) {
pll_type = intel_ddi_port_pll_type(&dig_port->base, crtc_state);
active_streams = 1;
}
if (active_streams && !tc_phy_is_connected(tc, pll_type))
drm_err(display->drm, "Port %s: PHY disconnected with %d active stream(s)\n",
tc->port_name, active_streams);
return active_streams;
}
/** * intel_tc_port_sanitize_mode: Sanitize the given port's TypeC mode * @dig_port: digital port * @crtc_state: atomic state of CRTC connected to @dig_port * * Sanitize @dig_port's TypeC mode wrt. the encoder's state right after driver * loading and system resume: * If the encoder is enabled keep the TypeC mode/PHY connected state locked until * the encoder is disabled. * If the encoder is disabled make sure the PHY is disconnected. * @crtc_state is valid if @dig_port is enabled, NULL otherwise.
*/ void intel_tc_port_sanitize_mode(struct intel_digital_port *dig_port, conststruct intel_crtc_state *crtc_state)
{ struct intel_display *display = to_intel_display(dig_port); struct intel_tc_port *tc = to_tc_port(dig_port);
mutex_lock(&tc->lock);
drm_WARN_ON(display->drm, tc->link_refcount != 1); if (!tc_port_has_active_streams(tc, crtc_state)) { /* * TBT-alt is the default mode in any case the PHY ownership is not * held (regardless of the sink's connected live state), so * we'll just switch to disconnected mode from it here without * a note.
*/ if (tc->init_mode != TC_PORT_TBT_ALT &&
tc->init_mode != TC_PORT_DISCONNECTED)
drm_dbg_kms(display->drm, "Port %s: PHY left in %s mode on disabled port, disconnecting it\n",
tc->port_name,
tc_port_mode_name(tc->init_mode));
tc_phy_disconnect(tc);
__intel_tc_port_put_link(tc);
}
/* * The type-C ports are different because even when they are connected, they may * not be available/usable by the graphics driver: see the comment on * icl_tc_phy_connect(). So in our driver instead of adding the additional * concept of "usable" and make everything check for "connected and usable" we * define a port as "connected" when it is not only connected, but also when it * is usable by the rest of the driver. That maintains the old assumption that * connected ports are usable, and avoids exposing to the users objects they * can't really use.
*/ bool intel_tc_port_connected(struct intel_encoder *encoder)
{ struct intel_display *display = to_intel_display(encoder); struct intel_digital_port *dig_port = enc_to_dig_port(encoder); struct intel_tc_port *tc = to_tc_port(dig_port);
u32 mask = ~0;
/* * Disconnect the given digital port from its TypeC PHY (handing back the * control of the PHY to the TypeC subsystem). This will happen in a delayed * manner after each aux transactions and modeset disables.
*/ staticvoid intel_tc_port_disconnect_phy_work(struct work_struct *work)
{ struct intel_tc_port *tc =
container_of(work, struct intel_tc_port, disconnect_phy_work.work);
mutex_lock(&tc->lock);
if (!tc->link_refcount)
intel_tc_port_update_mode(tc, 1, true);
mutex_unlock(&tc->lock);
}
/** * intel_tc_port_flush_work: flush the work disconnecting the PHY * @dig_port: digital port * * Flush the delayed work disconnecting an idle PHY.
*/ staticvoid intel_tc_port_flush_work(struct intel_digital_port *dig_port)
{
flush_delayed_work(&to_tc_port(dig_port)->disconnect_phy_work);
}
/* * The firmware will not update the HPD status of other TypeC ports * that are active in DP-alt mode with their sink disconnected, until * this port is disabled and its PHY gets disconnected. Make sure this * happens in a timely manner by disconnecting the PHY synchronously.
*/
intel_tc_port_flush_work(dig_port);
}
int intel_tc_port_init(struct intel_digital_port *dig_port, bool is_legacy)
{ struct intel_display *display = to_intel_display(dig_port); struct intel_tc_port *tc; enum port port = dig_port->base.port; enum tc_port tc_port = intel_encoder_to_tc(&dig_port->base);
if (drm_WARN_ON(display->drm, tc_port == TC_PORT_NONE)) return -EINVAL;
tc = kzalloc(sizeof(*tc), GFP_KERNEL); if (!tc) return -ENOMEM;
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.