Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -2020,6 +2020,8 @@ F: tools/diagnostics/apple-dptx-training-test.c
F: tools/diagnostics/apple-j700-dp-test.c
F: tools/diagnostics/display-attributes-fixture.json
F: tools/diagnostics/test-apple-afk-admission.py
F: tools/diagnostics/test-apple-atc-parking.py
F: tools/diagnostics/test-apple-atc-pipe.py
F: tools/diagnostics/test-apple-atc-unwind.py
F: tools/diagnostics/test-apple-dcp-readiness.py
F: tools/diagnostics/test-apple-dptx-readiness.py
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/apple/dcp-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ struct apple_dcp {
u64 external_rx_generation;
u64 external_stop_generation;
bool external_cable;
/* RX tombstone: initially unknown; only physical OOB events change it. */
bool external_physically_absent;
bool external_running;
bool external_power_attempted;
/* iomfb.lock: replay the latest callback after activation succeeds. */
Expand Down
6 changes: 5 additions & 1 deletion drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -2493,6 +2493,8 @@ static void dcp_external_report_disconnected(struct apple_dcp *dcp)
unsigned int action;

guard(mutex)(&dcp->iomfb.lock);
/* A paused callback from the old sink must not be replayed on wake. */
dcp->external_hotplug_pending = false;
action = dcp_mode_hotplug(&dcp->mode_state, false,
dcp->connector ? &dcp->connector->connected : NULL);
if (dcp->connector) {
Expand Down Expand Up @@ -2622,6 +2624,7 @@ static int dcp_external_disconnect(struct apple_dcp *dcp, bool physical)
guard(mutex)(&dcp->external_lock);
/* Invalidate physical EDID before waiting for firmware power/link teardown. */
if (physical) {
WRITE_ONCE(dcp->external_physically_absent, true);
scoped_guard(mutex, &dcp->hpd_mutex) {
WRITE_ONCE(dcp->typec_cable_connected, false);
dcp->typec_generation++;
Expand Down Expand Up @@ -2666,7 +2669,7 @@ bool dcp_external_hotplug(struct apple_dcp *dcp, bool connected)
return false;
/* No lifecycle or HPD mutex may be acquired from the RX callback. */
lockdep_assert_held(&dcp->iomfb.lock);
if (READ_ONCE(dcp->quiescing))
if (READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->external_physically_absent))
return true;
if (dcp->iomfb.presentation_paused) {
dcp->external_hotplug_pending = true;
Expand Down Expand Up @@ -2844,6 +2847,7 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port)
if (new_cable)
dcp->external_generation++;
dcp->external_cable = true;
WRITE_ONCE(dcp->external_physically_absent, false);
dcp_external_publish_generation(dcp);
scoped_guard(mutex, &dcp->hpd_mutex) {
WRITE_ONCE(dcp->typec_cable_connected, true);
Expand Down
124 changes: 90 additions & 34 deletions drivers/phy/apple/atc.c
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ struct atcphy_hw {
* @ss_tunables: The complete SuperSpeed tunable set was supplied
* @dp_tunables: The complete T8140 DisplayPort bundle was supplied
* @aux_power_requested: AUX power-up began and needs matching shutdown
* @mode_dirty: J700 mode transition needs cleanup before it can be retried
* @mode_dirty: Mode transition needs cleanup before it can be retried
* @dp_idle_pmas: J700 PMAs with an incomplete routing idle sequence
* @dp_owned_pmas: J700 PMAs requiring main-link cleanup
* @dp_spread_spectrum: J700 main-link spread-spectrum state
Expand Down Expand Up @@ -1085,12 +1085,11 @@ static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy)
u32 reg;

if (readl(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ) & PIPEHANDLER_LOCK_EN) {
dev_warn(atcphy->dev, "Pipehandler already locked\n");
return 0;
dev_dbg(atcphy->dev, "Pipehandler lock already requested\n");
} else {
set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN);
}

set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN);

ret = readl_poll_timeout(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg,
reg & PIPEHANDLER_LOCK_EN, 10, PIPEHANDLER_LOCK_ACK_TIMEOUT_US);
if (ret) {
Expand Down Expand Up @@ -1379,11 +1378,17 @@ static void atcphy_enable_dummy_phy(struct apple_atcphy *atcphy)

static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy)
{
bool live_usb3 = atcphy->hw->has_t8140_dp && atcphy->fixed_usb2 &&
atcphy->host_active &&
atcphy->pipe_state == ATCPHY_PIPEHANDLER_STATE_USB3;
int ret;

ret = atcphy_pipehandler_check(atcphy);
if (ret)
if (ret) {
if (live_usb3)
atcphy->mode_dirty = true;
return ret;
}

/* Force disable link detection */
clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES,
Expand All @@ -1392,16 +1397,28 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy)
set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT);

ret = atcphy_pipehandler_lock(atcphy);
if (ret)
if (ret) {
/* A live controller must acknowledge before its backend changes. */
if (live_usb3) {
atcphy->mode_dirty = true;
return ret;
}
dev_warn(atcphy->dev, "Failed to lock pipehandler");
}

/* Switch to dummy PHY */
atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY,
PIPEHANDLER_MUX_CTRL_CLK_DUMMY);

ret = atcphy_pipehandler_unlock(atcphy);
if (ret)
if (ret) {
/* The mux changed, but the handoff has not completed. Retain ownership. */
if (live_usb3) {
atcphy->mode_dirty = true;
return ret;
}
dev_warn(atcphy->dev, "Failed to unlock pipehandler");
}

mask32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE,
PIPEHANDLER_NATIVE_POWER_DOWN, FIELD_PREP(PIPEHANDLER_NATIVE_POWER_DOWN, 2));
Expand All @@ -1415,8 +1432,7 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy)

/*
* Route the PIPE to the backend the current mode needs. The state is recorded
* whether or not the sequence succeeded: the PIPE is configured once per mode,
* and a failed attempt is not retried on the next set_mode call.
* only after the sequence succeeds, so a failed attempt remains retryable.
*/
static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host)
{
Expand All @@ -1436,25 +1452,29 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host)
ret = atcphy_configure_pipehandler_dummy(atcphy);
break;
}
atcphy->pipe_state = state;
if (!ret)
atcphy->pipe_state = state;

return ret;
}

/* Route the PIPE back to the dummy backend unless it is there already */
static void atcphy_park_pipehandler(struct apple_atcphy *atcphy)
static int atcphy_park_pipehandler(struct apple_atcphy *atcphy)
{
int ret;

lockdep_assert_held(&atcphy->lock);

if (atcphy->pipe_state == ATCPHY_PIPEHANDLER_STATE_DUMMY)
return;
return 0;

ret = atcphy_configure_pipehandler_dummy(atcphy);
if (ret)
if (ret) {
dev_warn(atcphy->dev, "Failed to switch PIPE to dummy: %d\n", ret);
return ret;
}
atcphy->pipe_state = ATCPHY_PIPEHANDLER_STATE_DUMMY;
return 0;
}

static void atcphy_setup_pipehandler(struct apple_atcphy *atcphy)
Expand Down Expand Up @@ -1959,6 +1979,9 @@ static int atcphy_dp_configure(struct apple_atcphy *atcphy, enum atcphy_dp_link_
u32 reg;

guard(mutex)(&atcphy->lock);
/* A failed transition may have asserted the PHY resets already. */
if (atcphy->mode_dirty)
return -EBUSY;
/* the AUSPLL is in use as the Thunderbolt DP tunnel pixel clock */
if (atcphy->tunnel_clock_on || atcphy->tunnel_saved)
return -EBUSY;
Expand Down Expand Up @@ -2711,8 +2734,7 @@ static int atcphy_configure(struct apple_atcphy *atcphy, enum atcphy_mode mode)
return ret;
}
/* Keep failed transitions retryable even when the requested mode is unchanged. */
if (atcphy->hw->has_t8140_dp)
atcphy->mode_dirty = true;
atcphy->mode_dirty = true;
/* the mode change below may power the PHY down: stop the tunnel clock first */
atc_tunnel_stop_t8103(atcphy);
atc_tunnel_restore(atcphy);
Expand Down Expand Up @@ -2836,11 +2858,15 @@ static int atcphy_usb2_init(struct phy *phy)
{
struct apple_atcphy *atcphy = phy_get_drvdata(phy);

if (!atcphy->fixed_usb2)
return 0;

guard(mutex)(&atcphy->lock);

/* A delayed shutdown may have completed after the last Type-C event. */
if (!atcphy->fixed_usb2) {
if (atcphy->mode == APPLE_ATCPHY_MODE_OFF || atcphy->mode_dirty)
return atcphy_configure(atcphy, atcphy->typec_mode);
return 0;
}

/*
* dwc3 initialises its PHYs after releasing its reset, and asserting that
* reset powered the USB2 PHY off. A port behind a fixed hub gets no Type-C
Expand All @@ -2850,7 +2876,7 @@ static int atcphy_usb2_init(struct phy *phy)
* the Type-C mux last asked for, USB2 until a cable event says otherwise.
*/
set32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST);
if (atcphy->mode == APPLE_ATCPHY_MODE_OFF)
if (atcphy->mode == APPLE_ATCPHY_MODE_OFF || atcphy->mode_dirty)
return atcphy_configure(atcphy, atcphy->typec_mode);

atcphy_usb2_power_on(atcphy);
Expand All @@ -2867,15 +2893,20 @@ static const struct phy_ops apple_atc_usb2_phy_ops = {
static int atcphy_usb3_power_off(struct phy *phy)
{
struct apple_atcphy *atcphy = phy_get_drvdata(phy);
int ret;

guard(mutex)(&atcphy->lock);

atcphy_park_pipehandler(atcphy);
ret = atcphy_park_pipehandler(atcphy);
if (ret)
return ret;
if (atcphy->mode != APPLE_ATCPHY_MODE_OFF || atcphy->mode_dirty) {
ret = atcphy_configure(atcphy, APPLE_ATCPHY_MODE_OFF);
if (ret)
return ret;
}
atcphy->host_active = false;

if (atcphy->mode != APPLE_ATCPHY_MODE_OFF)
atcphy_configure(atcphy, APPLE_ATCPHY_MODE_OFF);

return 0;
}

Expand All @@ -2902,6 +2933,9 @@ static int atcphy_usb3_set_mode(struct phy *phy, enum phy_mode mode, int submode
* PIPE handler once. Nothing needs to be done either when the PIPE is already routed to
* the backend the current mode uses, which includes the dummy backend of the USB2 modes.
*/
/* A failed lane/domain transition must be restored before routing PIPE. */
if (atcphy->mode_dirty)
return -EBUSY;
if (atcphy->pipe_state == atcphy_modes[atcphy->mode].pipehandler_state)
return 0;

Expand Down Expand Up @@ -3122,11 +3156,16 @@ static void _atcphy_dwc3_reset_assert(struct apple_atcphy *atcphy)
static int atcphy_dwc3_reset_assert(struct reset_controller_dev *rcdev, unsigned long id)
{
struct apple_atcphy *atcphy = container_of(rcdev, struct apple_atcphy, rcdev);
int ret;

guard(mutex)(&atcphy->lock);

/* Do not reset the controller or USB2 PHY if PIPE parking fails. */
ret = atcphy_park_pipehandler(atcphy);
if (ret)
return ret;

_atcphy_dwc3_reset_assert(atcphy);
atcphy_park_pipehandler(atcphy);
atcphy->host_active = false;
atcphy_usb2_power_off(atcphy);

Expand Down Expand Up @@ -3172,19 +3211,30 @@ static int atcphy_probe_rcdev(struct apple_atcphy *atcphy)
static int atcphy_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
{
struct apple_atcphy *atcphy = typec_switch_get_drvdata(sw);
bool swap_lanes;

guard(mutex)(&atcphy->lock);

switch (orientation) {
case TYPEC_ORIENTATION_NONE:
break;
return 0;
case TYPEC_ORIENTATION_NORMAL:
atcphy->swap_lanes = false;
swap_lanes = false;
break;
case TYPEC_ORIENTATION_REVERSE:
atcphy->swap_lanes = true;
swap_lanes = true;
break;
default:
return -EINVAL;
}
/* The mode alone cannot describe a route after its orientation changes. */
if (atcphy->swap_lanes != swap_lanes) {
atcphy->swap_lanes = swap_lanes;
atcphy->mode_dirty = true;
}
dev_dbg(atcphy->dev, "orientation=%u mode=%u dirty=%u pipe=%u host=%u\n",
orientation, atcphy->mode, atcphy->mode_dirty,
atcphy->pipe_state, atcphy->host_active);

return 0;
}
Expand Down Expand Up @@ -3281,8 +3331,13 @@ static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *sta
target_mode = APPLE_ATCPHY_MODE_USB2;

dev_dbg(atcphy->dev, "mux target=%u\n", target_mode);
if (atcphy->mode == target_mode && !atcphy->mode_dirty)
if (atcphy->mode == target_mode && !atcphy->mode_dirty) {
/* Retry a failed PIPE setup without disturbing the configured lanes. */
if (atcphy->fixed_usb2 && atcphy->host_active &&
atcphy->pipe_state != atcphy_modes[target_mode].pipehandler_state)
return atcphy_configure_pipehandler(atcphy, true);
return 0;
}

switch (target_mode) {
case APPLE_ATCPHY_MODE_TBT:
Expand Down Expand Up @@ -3313,12 +3368,13 @@ static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *sta
/*
* The controller of a port behind a fixed hub stays up across mode
* changes, so the PIPE has to follow the mode from here rather than
* from a later set_mode call: park it before the lanes change to a
* mode without a USB3 backend, and route it to the new backend
* afterwards while dwc3 is up in host mode.
* from a later set_mode call. Even another USB3 mode can move the
* backend to the other PMA when orientation changes. Park before
* rebuilding the lanes, then reconnect while dwc3 is in host mode.
*/
if (pipe_state != ATCPHY_PIPEHANDLER_STATE_USB3)
atcphy_park_pipehandler(atcphy);
ret = atcphy_park_pipehandler(atcphy);
if (ret)
return ret;

ret = atcphy_configure(atcphy, target_mode);
if (ret)
Expand Down
Loading