Skip to content
Merged
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 Documentation/devicetree/bindings/usb/apple,sn201202x.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ properties:
minItems: 3
maxItems: 4

wakeup-source: true

connector:
$ref: /schemas/connector/usb-connector.yaml#

Expand Down
2 changes: 2 additions & 0 deletions arch/arm64/boot/dts/apple/t8132-j713.dts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@
<43 IRQ_TYPE_EDGE_RISING>,
<45 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "irq", "sleep", "wake";
wakeup-source;

typec1: connector {
compatible = "usb-c-connector";
Expand Down Expand Up @@ -345,6 +346,7 @@
<17 IRQ_TYPE_EDGE_RISING>,
<19 IRQ_TYPE_EDGE_RISING>;
interrupt-names = "irq", "sleep", "wake";
wakeup-source;

typec0: connector {
compatible = "usb-c-connector";
Expand Down
38 changes: 38 additions & 0 deletions drivers/phy/apple/atc-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,41 @@ static void atcphy_test_t8132_rpm_busy(struct kunit *test)
KUNIT_EXPECT_TRUE(test, atcphy_rpm_busy(atcphy));
}

#ifdef CONFIG_SUSPEND
/* An epoch with a host survives suspend-to-idle only; without one, or incomplete, never. */
static void atcphy_test_t8132_system_suspend(struct kunit *test)
{
struct apple_atcphy *atcphy = atcphy_test_alloc_powered_off(test);
suspend_state_t target = pm_suspend_target_state;

atcphy->hw = &atcphy_hw_t8132;
atcphy->t8132_ss = true;
dev_set_drvdata(atcphy->dev, atcphy);

pm_suspend_target_state = PM_SUSPEND_TO_IDLE;
atcphy->mode_dirty = true;
KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY);
atcphy->mode_dirty = false;

/* Without a host, as in a USB3 device role, nothing holds the domain */
atcphy->t8132_common_owned = true;
KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY);
KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended);

atcphy->host_active = true;
KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), 0);
KUNIT_EXPECT_TRUE(test, atcphy->t8132_suspended);
KUNIT_EXPECT_EQ(test, atcphy_resume(atcphy->dev), 0);
KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended);

pm_suspend_target_state = PM_SUSPEND_MEM;
KUNIT_EXPECT_EQ(test, atcphy_suspend(atcphy->dev), -EBUSY);
KUNIT_EXPECT_FALSE(test, atcphy->t8132_suspended);

pm_suspend_target_state = target;
}
#endif

/* The hold follows the busy state, with one reference at most. */
static void atcphy_test_t8132_rpm_hold(struct kunit *test)
{
Expand Down Expand Up @@ -1356,6 +1391,9 @@ static struct kunit_case atcphy_t8132_pm_test_cases[] = {
KUNIT_CASE(atcphy_test_t8132_rpm_hold),
KUNIT_CASE(atcphy_test_t8132_restore_idle),
KUNIT_CASE(atcphy_test_t8132_restore_mode),
#ifdef CONFIG_SUSPEND
KUNIT_CASE(atcphy_test_t8132_system_suspend),
#endif
{}
};

Expand Down
16 changes: 14 additions & 2 deletions drivers/phy/apple/atc.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <linux/reset-controller.h>
#include <linux/soc/apple/dp-tunnel.h>
#include <linux/soc/apple/tunable.h>
#include <linux/suspend.h>
#include <linux/types.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
Expand Down Expand Up @@ -6152,8 +6153,16 @@ static int atcphy_suspend(struct device *dev)
return 0;

guard(mutex)(&atcphy->lock);
/* Active and incomplete common epochs have no qualified sleep handoff. */
if (atcphy->t8132_common_owned || atcphy->mode_dirty)
/* An incomplete common epoch has no qualified sleep handoff. */
if (atcphy->mode_dirty)
return -EBUSY;
/*
* An active one survives suspend-to-idle while a host runs on it: DWC3
* then keeps the power domain on across system sleep, so the link stays
* as it is. Without a host, or in a deeper sleep state, it is lost.
*/
if (atcphy->t8132_common_owned &&
(!atcphy->host_active || pm_suspend_target_state != PM_SUSPEND_TO_IDLE))
return -EBUSY;
/* Do not admit a new epoch after this check and before resume. */
atcphy->t8132_suspended = true;
Expand All @@ -6172,6 +6181,9 @@ static int atcphy_resume(struct device *dev)

guard(mutex)(&atcphy->lock);
atcphy->t8132_suspended = false;
if (atcphy->t8132_common_owned && atcphy->host_active &&
READ_ONCE(atcphy->pd_was_off))
dev_warn(dev, "power domain went off under an active USB3 link\n");
/*
* A suspended PHY holds nothing, and its domain may drop again once system
* resume completes: leave the restore to its next operation.
Expand Down
4 changes: 4 additions & 0 deletions drivers/usb/core/quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,10 @@ static const struct usb_device_id usb_quirk_list[] = {
/* VIA Labs, Inc. USB2.0 Hub */
{ USB_DEVICE(0x2109, 0x2817), .driver_info = USB_QUIRK_NO_LPM },

/* VIA Labs, Inc. VL822 USB2.0 Hub */
{ USB_DEVICE(0x2109, 0x2822), .driver_info =
USB_QUIRK_HUB_NO_SUSPENDED_WAKEUP },

/* Raydium Touchscreen */
{ USB_DEVICE(0x2386, 0x3114), .driver_info = USB_QUIRK_NO_LPM },

Expand Down
14 changes: 14 additions & 0 deletions drivers/usb/core/usb-quirks-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ static void usb_quirks_vl122_test(struct kunit *test)
KUNIT_EXPECT_TRUE(test, usb_hub_id_quirks(&t->intf) & HUB_QUIRK_DISABLE_AUTOSUSPEND);
}

/*
* The VIA Labs VL822 USB 2.0 hub passes on no remote wakeup from below while it
* is suspended. It is kept awake in suspend-to-idle and keeps its autosuspend.
*/
static void usb_quirks_vl822_test(struct kunit *test)
{
struct usb_quirks_test *t = usb_quirks_test_hub(test, 0x2109, 0x2822);

KUNIT_EXPECT_EQ(test, usb_detect_device_list_quirks(&t->udev),
USB_QUIRK_HUB_NO_SUSPENDED_WAKEUP);
KUNIT_EXPECT_EQ(test, usb_hub_id_quirks(&t->intf), 0UL);
}

/* Another VIA Labs hub keeps its own device quirk and no hub quirk. */
static void usb_quirks_other_via_hub_test(struct kunit *test)
{
Expand All @@ -73,6 +86,7 @@ static void usb_quirks_generic_hub_test(struct kunit *test)

static struct kunit_case usb_quirks_test_cases[] = {
KUNIT_CASE(usb_quirks_vl122_test),
KUNIT_CASE(usb_quirks_vl822_test),
KUNIT_CASE(usb_quirks_other_via_hub_test),
KUNIT_CASE(usb_quirks_generic_hub_test),
{}
Expand Down
16 changes: 16 additions & 0 deletions drivers/usb/dwc3/dwc3-apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
* @idle_rpm: Let the controller runtime-suspend while the core is down
* @rpm_held: The glue holds a runtime PM reference for the running core
* @pre_core_rpm: Runtime PM is the glue's own until the first core bring-up
* @dt_dis_u3_susphy: The device tree keeps USB3 PHY suspend off itself
* @drd_cfg: The controller's configuration block (optional, for its tunable)
* @tunables: Controller tunables from the device tree, see dwc3_apple_tunable_list
*/
Expand Down Expand Up @@ -154,6 +155,7 @@ struct dwc3_apple {
bool idle_rpm;
bool rpm_held;
bool pre_core_rpm;
bool dt_dis_u3_susphy;

void __iomem *drd_cfg;
struct apple_tunable *tunables[DWC3_APPLE_NR_TUNABLES];
Expand Down Expand Up @@ -435,6 +437,8 @@ static int dwc3_apple_core_stop(struct dwc3_apple *appledwc)
lockdep_assert_held(&appledwc->lock);
if (appledwc->core_active) {
if (!appledwc->core_exit_pending) {
/* The PHY hands the PIPE back in P3, as it took it over */
dwc->dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy;
dwc3_enable_susphy(dwc, true);
dwc3_event_buffers_cleanup(dwc);
appledwc->core_exit_pending = true;
Expand Down Expand Up @@ -619,13 +623,22 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat
* device mode and the T8132 host role were selected in dwc3_apple_core_start();
* other host roles are selected by the xHCI root hub as it starts.
*/
appledwc->dwc.dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy;
dwc3_enable_susphy(&appledwc->dwc, true);
ret = phy_set_mode(appledwc->usb3_phy, host ? PHY_MODE_USB_HOST : PHY_MODE_USB_DEVICE);
if (ret) {
/* A failed T8132 BIST or PIPE handoff must not start xHCI. */
if (of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3"))
return ret;
dev_warn(appledwc->dev, "USB3 PHY setup failed, USB2 only, err=%d\n", ret);
} else if (host && appledwc->usb3_phy &&
of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3")) {
/*
* The T8132 PHY does not bring a SuperSpeed link back from U3 once
* the core has suspended it: keep it out of P3 from here on.
*/
appledwc->dwc.dis_u3_susphy_quirk = true;
dwc3_enable_susphy(&appledwc->dwc, true);
} else if (appledwc->usb3_phy &&
of_device_is_compatible(appledwc->dev->of_node, "apple,t6040-dwc3")) {
dwc3_apple_usb3_pipe_reset(appledwc);
Expand Down Expand Up @@ -728,6 +741,8 @@ static int dwc3_apple_exit(struct dwc3_apple *appledwc)
appledwc->state = DWC3_APPLE_NO_CABLE;
break;
case DWC3_APPLE_HOST:
/* dwc3_host_exit() lets the USB3 PHY into P3 for the PIPE handback */
appledwc->dwc.dis_u3_susphy_quirk = appledwc->dt_dis_u3_susphy;
dwc3_host_exit(&appledwc->dwc);
appledwc->state = DWC3_APPLE_NO_CABLE;
break;
Expand Down Expand Up @@ -855,6 +870,7 @@ static int dwc3_apple_probe(struct platform_device *pdev)
appledwc->dev = &pdev->dev;
appledwc->fixed_hub = dwc3_apple_has_fixed_hub(dev);
appledwc->host_only = dwc3_apple_host_only(dev);
appledwc->dt_dis_u3_susphy = device_property_read_bool(dev, "snps,dis_u3_susphy_quirk");
/* The PHY driver and the DARTs follow runtime PM on these SoCs. */
appledwc->idle_rpm = of_device_is_compatible(dev->of_node, "apple,t6040-dwc3") ||
of_device_is_compatible(dev->of_node, "apple,t8132-dwc3");
Expand Down
10 changes: 6 additions & 4 deletions drivers/usb/typec/tipd/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ config TYPEC_TIPD_KUNIT_TEST

config TYPEC_SN201202X_KUNIT_TEST
tristate "KUnit tests for SN201202x" if !KUNIT_ALL_TESTS
depends on KUNIT && TYPEC_SN201202X
depends on KUNIT && TYPEC_SN201202X && SUSPEND
select REGMAP
default KUNIT_ALL_TESTS
help
Test that the SN201202x events are not handled while the controller
sleeps, and that a system suspend that fails leaves the controller
awake. These tests run on a simulated SPMI bus, without accessing a
physical Type-C controller.
sleeps, that a system suspend that fails leaves the controller awake,
and that an idle controller sleeps and wakes for register access and
for system suspend. Also test which data status and status count as
a link that wakes the system from suspend-to-idle. These tests run on
a simulated SPMI bus, without accessing a physical Type-C controller.
76 changes: 67 additions & 9 deletions drivers/usb/typec/tipd/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <linux/of.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/unaligned.h>
#include <linux/usb/pd.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_altmode.h>
#include <linux/usb/typec_dp.h>
Expand Down Expand Up @@ -46,6 +48,7 @@
#define TPS_REG_SYSTEM_CONF 0x28
#define TPS_REG_CTRL_CONF 0x29
#define TPS_REG_BOOT_STATUS 0x2D
#define TPS_REG_RX_SOURCE_CAPS 0x30
#define TPS_REG_POWER_STATUS 0x3f
#define TPS_REG_PD_STATUS 0x40
#define TPS_REG_RX_IDENTITY_SOP 0x48
Expand Down Expand Up @@ -1532,10 +1535,11 @@ static void cd321x_resume_work(struct work_struct *work)

if (!tps6598x_read_status(tps, &status))
goto retry;
/* Without a plug too, or a power supply removed in sleep stays online */
if (!tps6598x_read_power_status(tps))
goto retry;

if (status & TPS_STATUS_PLUG_PRESENT) {
if (!tps6598x_read_power_status(tps))
goto retry;
if (TPS_POWER_STATUS_PWROPMODE(tps->pwr_status) == TYPEC_PWR_MODE_PD &&
tps6598x_read_partner_identity(tps))
goto retry;
Expand Down Expand Up @@ -1645,6 +1649,46 @@ static int cd321x_check_setup(struct cd321x *cd321x, u64 event)
return 0;
}

/**
* cd321x_sleep_events - Acknowledge the events of a controller in system sleep
* @tps: The controller, woken for this
* @st: Its data status and status, and for a power sink whether its source is
* capable of USB communication
*
* The resume revalidates the whole port state, so nothing else is read.
*
* Context: tps->lock held.
* Return: 0, or a negative error code.
*/
int cd321x_sleep_events(struct tps6598x *tps, struct cd321x_sleep_state *st)
{
/* The PDO count, then the first PDO, vSafe5V, which carries the flags */
u8 caps[1 + sizeof(u32)];
u64 event;
int ret;

lockdep_assert_held(&tps->lock);
st->usb_comm = false;
ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event);
if (!ret && event)
ret = tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
if (ret)
return ret;
/* A controller that was reset is set up again on resume */
cd321x_check_setup(container_of(tps, struct cd321x, tps), event);

ret = tps6598x_read32(tps, TPS_REG_DATA_STATUS, &st->data_status);
if (!ret)
ret = tps6598x_read32(tps, TPS_REG_STATUS, &st->status);
if (!ret && !(st->status & TPS_STATUS_PORTROLE)) {
ret = tps6598x_block_read(tps, TPS_REG_RX_SOURCE_CAPS, caps, sizeof(caps));
if (!ret && (caps[0] & GENMASK(2, 0)))
st->usb_comm = get_unaligned_le32(&caps[1]) & PDO_FIXED_USB_COMM;
}
return ret;
}
EXPORT_SYMBOL_GPL(cd321x_sleep_events);

static irqreturn_t cd321x_interrupt(int irq, void *data)
{
struct tps6598x *tps = data;
Expand All @@ -1653,6 +1697,9 @@ static irqreturn_t cd321x_interrupt(int irq, void *data)
u32 status;
int ret;

if (tps->sleep_event && tps->sleep_event(tps))
return IRQ_HANDLED;

mutex_lock(&tps->lock);

ret = tps6598x_read64(tps, TPS_REG_INT_EVENT1, &event);
Expand Down Expand Up @@ -2951,10 +2998,17 @@ int tipd_init(struct tps6598x *tps)
}
}

tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source");
if (tps->irq) {
/*
* A variant that filters its events in system sleep keeps the
* interrupt live and decides itself whether to wake the system.
*/
ret = devm_request_threaded_irq(tps->dev, tps->irq, NULL,
tps->data->irq_handler,
IRQF_SHARED | IRQF_ONESHOT,
IRQF_SHARED | IRQF_ONESHOT |
(tps->wakeup && tps->sleep_event ?
IRQF_NO_SUSPEND : 0),
dev_name(tps->dev), tps);
} else {
dev_dbg(tps->dev, "no IRQ specified, using polling mode\n");
Expand All @@ -2968,10 +3022,10 @@ int tipd_init(struct tps6598x *tps)

fwnode_handle_put(fwnode);

tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source");
if (tps->wakeup && tps->irq) {
devm_device_init_wakeup(tps->dev);
enable_irq_wake(tps->irq);
if (!tps->sleep_event)
enable_irq_wake(tps->irq);
}

tipd_debugfs_register(tps);
Expand Down Expand Up @@ -3040,8 +3094,10 @@ EXPORT_SYMBOL_GPL(tipd_remove);
int tipd_suspend(struct tps6598x *tps)
{
if (tps->wakeup) {
disable_irq(tps->irq);
enable_irq_wake(tps->irq);
if (!tps->sleep_event) {
disable_irq(tps->irq);
enable_irq_wake(tps->irq);
}
} else if (tps->reset) {
gpiod_set_value_cansleep(tps->reset, 1);
}
Expand Down Expand Up @@ -3071,8 +3127,10 @@ int tipd_resume(struct tps6598x *tps)
}

if (tps->wakeup) {
disable_irq_wake(tps->irq);
enable_irq(tps->irq);
if (!tps->sleep_event) {
disable_irq_wake(tps->irq);
enable_irq(tps->irq);
}
} else if (tps->reset) {
gpiod_set_value_cansleep(tps->reset, 0);
msleep(TPS_SETUP_MS);
Expand Down
Loading