From nobody Mon Jun 8 06:39:48 2026 Received: from outbound11.mail.transip.nl (outbound11.mail.transip.nl [136.144.136.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8D1C235B631; Sat, 6 Jun 2026 12:34:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=136.144.136.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780749284; cv=none; b=OhmGuZ9hSgAb5NGn4/ndkm9NrA0U2Kjv8/WHy+0jOzzjrR5kpxgsUA6zigA2lkF1VCFzXHNI9ooG3J1vLN5QXA7KUCfxcwox7Z8w4bDg60ovEspSdYP4rBOUkjiVoQna7dpot1VlA6pg/aFdDCk57spEz2jbwYOBezn1qiyhQNY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780749284; c=relaxed/simple; bh=DSEE5W+bUq/qz4HSuUOBsJojBrNkZwP6W5Me/2XZooo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LSH11fagiLruDqcWDFHNgPK01V9+W1mZElJeP/76oNTqFDH5WkLQ6FBUIHeEN9Efewvc3VALdZWaKnm638YyGPdImKu2ko9MiZjO0DCh+FHnN0zi3lZfKxK9Ngo5wxixaB/8urxIpAHvij9HE+aLYTTx1+7doICh+zpNTx7iZy0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org; spf=pass smtp.mailfrom=herrie.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b=tcGjbA4D; arc=none smtp.client-ip=136.144.136.18 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=herrie.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b="tcGjbA4D" Received: from submission1.mail.transip.nl (unknown [10.100.4.70]) by outbound11.mail.transip.nl (Postfix) with ESMTP id 4gXd6C58G0zkQNln; Sat, 6 Jun 2026 14:34:27 +0200 (CEST) Received: from [127.0.1.1] (180-93-184-31.ftth.glasoperator.nl [31.184.93.180]) by submission1.mail.transip.nl (Postfix) with ESMTPA id 4gXd6C03XnzJjhYM; Sat, 6 Jun 2026 14:34:26 +0200 (CEST) From: Herman van Hazendonk Date: Sat, 06 Jun 2026 14:34:26 +0200 Subject: [PATCH v2 1/2] clk: qcom: gdsc: add LEGACY_FOOTSWITCH support for MSM8x60 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-1-187a32d2f015@herrie.org> References: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-0-187a32d2f015@herrie.org> In-Reply-To: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-0-187a32d2f015@herrie.org> To: Bjorn Andersson , Konrad Dybcio , Stephen Boyd , Michael Turquette Cc: linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Herman van Hazendonk X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780749266; l=17570; i=github.com@herrie.org; s=20240417; h=from:subject:message-id; bh=DSEE5W+bUq/qz4HSuUOBsJojBrNkZwP6W5Me/2XZooo=; b=QUvLveF2BYHpMrW9XxreCBfkn3jumUiycHuB7LexC5MyM7gypAn1DH6gcsIXaNoOtSZOMLW3k K13Qk6pXvgNBQpRPba3yv/C+l2RW7wnwBkAzKclcOedLu0Zx1vKQ8bk X-Developer-Key: i=github.com@herrie.org; a=ed25519; pk=YYxdq8fb5O9vhkW3n2dCH044FPZZO5718v/du7fRhFw= X-Scanned-By: ClueGetter at submission1.mail.transip.nl DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=transip-a; d=herrie.org; t=1780749267; h=from:subject:to:cc: references:in-reply-to:date:mime-version:content-type; bh=zO2CAJBh9pdl2OXKCjRyvHeHsuKRc1CG5wQZ8yl072M=; b=tcGjbA4DP4Wjt679IpwIceLvre6yDUi6uX4mRfyqYbKiVLVbm1Zni8DPasGPrj6r8ZwTkl kqPMYlqhP53NLlIEXSmQfGwhHQRd8c+qKD6tByJ+H/VZidcoNk33LxoXQUUeHmjT8IV4+f 3CkYwhT6wF191JRQbAs/EQIYsCc8Ml2t3m+X/2zsyQDQRqTdXDhsHuo/sAfQnz44oiz9tJ uIZ/scww+HNi3P9yd00fo2Mt/IvKEpzTSh6tIwzxM//JZlNY00M8y+aKT9/10ItNhp4hul TSPNBHO6ZXaTxMnL6vUhk46dOg8mCoLe8TDfsbcZiTCEkEUtIuiX/eFA60U8Wg== X-Report-Abuse-To: abuse@transip.nl The MSM8x60 family (MSM8260, MSM8660, APQ8060) ships an older footswitch (FS / "GFS") block that pre-dates the GDSC programming model the existing driver was designed around. Adding GDSC entries for that family's MMCC power domains needs the driver to understand the legacy register layout: - the CLAMP, ENABLE and RETENTION bits live in the main GDSCR register rather than in a separate clamp_io_ctrl; - there is no power-status bit, so software cannot poll for the transition completing and has to gate progress on a fixed udelay() after toggling ENABLE; - ENABLE is positive-logic (set to power up, clear to collapse) rather than the modern inverted SW_COLLAPSE semantics; - none of the modern wait-time / HW-trigger / SW-override fields are present, so gdsc_init() must skip the wait-config programming block entirely. Introduce a LEGACY_FOOTSWITCH flag and the matching code paths in gdsc_check_status(), gdsc_update_collapse_bit(), gdsc_enable(), gdsc_disable() and gdsc_init(). The enable / disable sequences mirror what the downstream vendor footswitch driver did on these SoCs, with the regulator hand-off normally done by gdsc_toggle_logic() spliced in at the matching points so a legacy domain that has a parent regulator still gets voted up/down: enable: regulator_enable -> assert resets -> set ENABLE -> 2us settle -> deassert resets -> clear CLAMP -> 5us settle disable: assert resets -> set CLAMP -> clear ENABLE -> regulator_disable Every register-write failure inside the sequence walks the partial state back to a defined endpoint before returning the error, so a mid-sequence failure cannot strand the block in "rail powered but clamped + in reset" (or the symmetric "clamped + reset asserted + ENABLE still set"). The two pwrsts =3D=3D PWRSTS_ON short-circuits at the top of gdsc_enable() / gdsc_disable() are gated on !(sc->flags & LEGACY_FOOTSWITCH) for the same reason: a legacy footswitch declared PWRSTS_ON still has a real rail and clamp, which the reset-only fast path would silently skip. gdsc_init()'s PWRSTS_ON force-enable goes through gdsc_enable() for legacy domains for the same reason. In gdsc_init(), clear the RETENTION bit (BIT 9) before jumping to the common state-sync block. The vendor MSM8x60 footswitch driver does the same one-shot clear at probe for every footswitch; without it the reset-default value is unspecified per board and a stuck-set retention bit would leave the rail draining power while looking collapsed in software. This patch only adds the infrastructure; the MSM8x60 MMCC driver that consumes it lands in a follow-up series. Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5 Signed-off-by: Herman van Hazendonk --- drivers/clk/qcom/gdsc.c | 323 ++++++++++++++++++++++++++++++++++++++++++++= ++-- drivers/clk/qcom/gdsc.h | 7 ++ 2 files changed, 321 insertions(+), 9 deletions(-) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index 95aa07120245..1f80bebe88c7 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -27,6 +27,11 @@ #define GMEM_CLAMP_IO_MASK BIT(0) #define GMEM_RESET_MASK BIT(4) =20 +/* Legacy MSM8x60 footswitch register bits (single register layout) */ +#define LEGACY_FS_CLAMP_MASK BIT(5) +#define LEGACY_FS_ENABLE_MASK BIT(8) +#define LEGACY_FS_RETENTION_MASK BIT(9) + /* CFG_GDSCR */ #define GDSC_POWER_UP_COMPLETE BIT(16) #define GDSC_POWER_DOWN_COMPLETE BIT(15) @@ -63,6 +68,32 @@ static int gdsc_check_status(struct gdsc *sc, enum gdsc_= status status) u32 val; int ret; =20 + /* + * Legacy footswitches have no power-status bit: software has to + * infer the state from the ENABLE bit it just wrote. + */ + if (sc->flags & LEGACY_FOOTSWITCH) { + ret =3D regmap_read(sc->regmap, sc->gdscr, &val); + if (ret) + return ret; + /* + * A block with ENABLE=3D1 but CLAMP=3D1 is electrically isolated: + * the rail is powered but all I/O is clamped. The downstream + * vendor footswitch driver (footswitch-8x60.c) treats the block + * as "ON" only when ENABLE is set AND CLAMP is clear -- mirror + * that convention so callers don't mistake a clamped-but- + * powered block for a fully usable one. + */ + switch (status) { + case GDSC_ON: + return (val & (LEGACY_FS_ENABLE_MASK | LEGACY_FS_CLAMP_MASK)) + =3D=3D LEGACY_FS_ENABLE_MASK; + case GDSC_OFF: + return !(val & LEGACY_FS_ENABLE_MASK); + } + return -EINVAL; + } + if (sc->flags & POLL_CFG_GDSCR) reg =3D sc->gdscr + CFG_GDSCR_OFFSET; else if (sc->gds_hw_ctrl) @@ -121,6 +152,18 @@ static int gdsc_update_collapse_bit(struct gdsc *sc, b= ool val) u32 reg, mask; int ret; =20 + /* + * Legacy footswitches do not have an inverted SW_COLLAPSE bit; + * instead the same bit means ENABLE: clear to disable the rail, + * set to enable it. Invert the caller's "collapse" intent. + */ + if (sc->flags & LEGACY_FOOTSWITCH) { + reg =3D sc->gdscr; + mask =3D LEGACY_FS_ENABLE_MASK; + return regmap_update_bits(sc->regmap, reg, mask, + val ? 0 : mask); + } + if (sc->collapse_mask) { reg =3D sc->collapse_ctrl; mask =3D sc->collapse_mask; @@ -240,6 +283,23 @@ static inline void gdsc_assert_clamp_io(struct gdsc *s= c) GMEM_CLAMP_IO_MASK, 1); } =20 +/* + * Legacy MSM8x60 footswitches keep the I/O clamp bit in the main GDSCR + * (no separate clamp_io_ctrl register), so the helpers here use sc->gdscr. + */ +static inline int legacy_fs_deassert_clamp(struct gdsc *sc) +{ + return regmap_update_bits(sc->regmap, sc->gdscr, + LEGACY_FS_CLAMP_MASK, 0); +} + +static inline int legacy_fs_assert_clamp(struct gdsc *sc) +{ + return regmap_update_bits(sc->regmap, sc->gdscr, + LEGACY_FS_CLAMP_MASK, + LEGACY_FS_CLAMP_MASK); +} + static inline void gdsc_assert_reset_aon(struct gdsc *sc) { regmap_update_bits(sc->regmap, sc->clamp_io_ctrl, @@ -259,11 +319,121 @@ static void gdsc_retain_ff_on(struct gdsc *sc) static int gdsc_enable(struct generic_pm_domain *domain) { struct gdsc *sc =3D domain_to_gdsc(domain); - int ret; + int ret, rc; =20 - if (sc->pwrsts =3D=3D PWRSTS_ON) + /* + * Modern PWRSTS_ON-only GDSCs are pure reset-controllers: there + * is no rail to bring up so only the reset deassert is needed. + * Legacy footswitches always need the full power-up + clamp- + * release sequence below, even when declared PWRSTS_ON, so they + * must not take this short-circuit. + */ + if (sc->pwrsts =3D=3D PWRSTS_ON && !(sc->flags & LEGACY_FOOTSWITCH)) return gdsc_deassert_reset(sc); =20 + /* + * Legacy MSM8x60 footswitch enable sequence: + * 0. enable the parent regulator supply (if any) + * 1. assert per-block resets (if SW_RESET) + * 2. set ENABLE in GDSCR to power up the rail + * 3. wait 2us for the rail to fully charge + * 4. deassert resets + * 5. clear CLAMP in GDSCR to release the I/O clamp + * 6. wait 5us for clamps to release and signals to settle + * + * No status-bit polling -- the hardware does not expose one, so + * the fixed delays below are the only safe synchronisation point. + */ + if (sc->flags & LEGACY_FOOTSWITCH) { + if (sc->rsupply) { + ret =3D regulator_enable(sc->rsupply); + if (ret < 0) + return ret; + } + + if (sc->flags & SW_RESET) { + gdsc_assert_reset(sc); + /* + * Wait for synchronous resets to propagate before + * raising ENABLE: matches footswitch-8x60.c's + * udelay(RESET_DELAY_US) between assert and enable. + */ + udelay(1); + } + + ret =3D gdsc_update_collapse_bit(sc, false); + if (ret) { + /* + * Power-up write failed -- release the reset we + * just asserted so the block does not stay stuck + * in reset for the rest of the system's lifetime, + * and roll back the regulator vote we just took. + */ + if (sc->flags & SW_RESET) + gdsc_deassert_reset(sc); + if (sc->rsupply) + regulator_disable(sc->rsupply); + return ret; + } + + udelay(2); + + /* + * Release the I/O clamp BEFORE deasserting resets: the + * downstream vendor footswitch driver (footswitch-8x60.c) + * always clears CLAMP_BIT first, then deasserts per-block + * resets. This lets the block's outputs settle in a known + * reset state before they become visible to consumers. + */ + ret =3D legacy_fs_deassert_clamp(sc); + if (ret) { + /* + * Rail is already powered up; if we cannot release + * the I/O clamp, collapse the rail again to avoid + * leaving the block live but isolated, re-assert + * the reset so the block ends in a defined + * power-off state, and undo the regulator vote. + * Errors from these best-effort rollback steps are + * reported but do not override the original error + * returned to the caller -- the secondary failure + * means the hardware state is already indeterminate + * and the regulator vote must still be released. + */ + rc =3D gdsc_update_collapse_bit(sc, true); + if (rc) { + /* + * Collapse also failed: the rail is still ON. + * Do NOT call regulator_disable() -- the rail + * is still drawing from the supply and cutting + * it while ENABLE is set risks hardware damage. + * Mirror gdsc_disable()'s collapse-failure path + * which deliberately skips regulator_disable() + * when the rail did not collapse. + */ + pr_err("%s: rail collapse rollback failed (%d) after clamp release fai= lure (%d); rail may be ON, regulator vote leaked\n", + sc->pd.name, rc, ret); + } else { + if (sc->flags & SW_RESET) + gdsc_assert_reset(sc); + if (sc->rsupply) { + rc =3D regulator_disable(sc->rsupply); + if (rc) + pr_err("%s: regulator_disable failed (%d) in clamp-release rollback\= n", + sc->pd.name, rc); + } + } + return ret; + } + + /* Deassert resets now that clamp is released (vendor order). */ + if (sc->flags & SW_RESET) + gdsc_deassert_reset(sc); + + udelay(5); + + return 0; + } + if (sc->flags & SW_RESET) { gdsc_assert_reset(sc); udelay(1); @@ -317,11 +487,91 @@ static int gdsc_enable(struct generic_pm_domain *doma= in) static int gdsc_disable(struct generic_pm_domain *domain) { struct gdsc *sc =3D domain_to_gdsc(domain); - int ret; + int ret, rc; =20 - if (sc->pwrsts =3D=3D PWRSTS_ON) + /* + * Symmetric to gdsc_enable: modern PWRSTS_ON-only GDSCs only + * need a reset assert, but legacy footswitches with PWRSTS_ON + * still need to clamp I/O and collapse the rail explicitly so + * they must not take this short-circuit. + */ + if (sc->pwrsts =3D=3D PWRSTS_ON && !(sc->flags & LEGACY_FOOTSWITCH)) return gdsc_assert_reset(sc); =20 + /* + * Legacy MSM8x60 footswitch disable sequence: + * 1. assert per-block resets (if SW_RESET) + * 2. set CLAMP in GDSCR to hold I/O at safe values across collapse + * 3. clear ENABLE in GDSCR to collapse the rail + * 4. drop the parent regulator vote (if any) + */ + if (sc->flags & LEGACY_FOOTSWITCH) { + if (sc->flags & SW_RESET) { + gdsc_assert_reset(sc); + /* + * Wait for synchronous resets to propagate before + * clamping I/O: footswitch-8x60.c udelay(RESET_DELAY_US) + * between assert and CLAMP_BIT set. + */ + udelay(1); + } + + ret =3D legacy_fs_assert_clamp(sc); + if (ret) { + /* + * Clamp programming failed -- release the reset we + * just asserted so the block is not stranded in + * reset, then surface the error. + */ + if (sc->flags & SW_RESET) + gdsc_deassert_reset(sc); + return ret; + } + + ret =3D gdsc_update_collapse_bit(sc, true); + if (ret) { + /* + * Collapse failed -- the rail is still ON. Walk + * back the clamp and reset so the block returns + * to its enabled state rather than being stranded + * in the half-disabled "clamped + reset + on" + * state; the regulator vote stays in place because + * the rail is still drawing from it. A secondary + * failure of the clamp release is reported but + * cannot override the original error: the rail is + * still ON, so the caller's view ("disable failed, + * leave ON") is the correct outcome regardless. + */ + rc =3D legacy_fs_deassert_clamp(sc); + if (rc) + pr_err("%s: clamp release rollback failed (%d) after rail collapse fai= lure (%d); hw may be clamped+ON\n", + sc->pd.name, rc, ret); + if (sc->flags & SW_RESET) + gdsc_deassert_reset(sc); + return ret; + } + + if (sc->rsupply) { + ret =3D regulator_disable(sc->rsupply); + if (ret < 0) { + /* + * The rail is already collapsed. Reporting + * the regulator error to genpd would leave it + * thinking the domain is still ON when the + * silicon is in fact off; the next consumer + * enable would then be no-op'd by genpd and + * hit dead hardware. Better to leak the + * regulator vote (visible via /sys/.../ + * regulator) than to corrupt genpd state. + */ + pr_err("%s: regulator_disable failed (%d) after rail collapse; vote le= aked, genpd state kept consistent with silicon\n", + sc->pd.name, ret); + } + } + + return 0; + } + /* Turn off HW trigger mode if supported */ if (sc->flags & HW_CTRL) { ret =3D gdsc_hwctrl(sc, false); @@ -403,7 +653,28 @@ static bool gdsc_get_hwmode(struct generic_pm_domain *= domain, struct device *dev static int gdsc_init(struct gdsc *sc) { u32 mask, val; - int on, ret; + int initial_on, on, ret; + + /* + * Legacy MSM8x60 footswitches share none of the modern GDSC + * wait-time fields and have no HW trigger / SW override bits at + * all, so skip the wait-config programming and jump straight to + * the common state-sync block below. + * + * Clear the retention bit (BIT 9) so subsequent disable actually + * power-collapses the rail rather than holding state. The vendor + * MSM8x60 footswitch driver does the same one-shot clear at probe + * for every footswitch; without it the reset-default value is + * unspecified per board and a stuck-set retention bit would leave + * the rail draining power while looking collapsed in software. + */ + if (sc->flags & LEGACY_FOOTSWITCH) { + ret =3D regmap_update_bits(sc->regmap, sc->gdscr, + LEGACY_FS_RETENTION_MASK, 0); + if (ret) + return ret; + goto skip_wait_config; + } =20 /* * Disable HW trigger: collapse/restore occur based on registers writes. @@ -428,9 +699,31 @@ static int gdsc_init(struct gdsc *sc) if (ret) return ret; =20 - /* Force gdsc ON if only ON state is supported */ +skip_wait_config: + /* + * Sample the GDSC power state BEFORE any probe-time enable below + * so the "sync the kernel state" regulator vote only runs when the + * GDSC was already on at probe (bootloader handoff). For the + * PWRSTS_ON / ALWAYS_ON force-enable paths, gdsc_enable() and + * gdsc_toggle_logic() take the vote themselves -- re-voting from + * the sync block would double-vote rsupply and leak a reference. + */ + initial_on =3D gdsc_check_status(sc, GDSC_ON); + if (initial_on < 0) + return initial_on; + + /* + * Force gdsc ON if only ON state is supported. For legacy + * footswitches, gdsc_toggle_logic() would only flip the ENABLE + * bit and skip the I/O-clamp release + settle delay that the + * MSM8x60 power-up sequence requires; call gdsc_enable() instead + * so the full legacy sequence runs. + */ if (sc->pwrsts =3D=3D PWRSTS_ON) { - ret =3D gdsc_toggle_logic(sc, GDSC_ON, false); + if (sc->flags & LEGACY_FOOTSWITCH) + ret =3D gdsc_enable(&sc->pd); + else + ret =3D gdsc_toggle_logic(sc, GDSC_ON, false); if (ret) return ret; } @@ -440,8 +733,20 @@ static int gdsc_init(struct gdsc *sc) return on; =20 if (on) { - /* The regulator must be on, sync the kernel state */ - if (sc->rsupply) { + /* + * Sync the kernel regulator state only if the GDSC was + * already on at probe; if we just enabled it above, the + * vote was taken inside gdsc_enable() / gdsc_toggle_logic(). + * + * Special case: PWRSTS_ON + LEGACY_FOOTSWITCH always routes + * through gdsc_enable() above (lines around the PWRSTS_ON + * block), which calls regulator_enable() unconditionally on + * the legacy path. Skip the sync vote in that case to avoid + * a double-vote that gdsc_disable() only unwinds once. + */ + if (sc->rsupply && initial_on && + !(sc->pwrsts =3D=3D PWRSTS_ON && + (sc->flags & LEGACY_FOOTSWITCH))) { ret =3D regulator_enable(sc->rsupply); if (ret < 0) return ret; diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h index dd843e86c05b..13ca09f93a01 100644 --- a/drivers/clk/qcom/gdsc.h +++ b/drivers/clk/qcom/gdsc.h @@ -68,6 +68,13 @@ struct gdsc { #define RETAIN_FF_ENABLE BIT(7) #define NO_RET_PERIPH BIT(8) #define HW_CTRL_TRIGGER BIT(9) +/* + * Legacy MSM8x60-family footswitch (a.k.a. "GFS"). Different register lay= out + * from the modern GDSC blocks: CLAMP at bit 5, ENABLE at bit 8, RETENTION= at + * bit 9, and there is no power-status bit so software has to assume the + * transition completed after a fixed delay rather than polling status. + */ +#define LEGACY_FOOTSWITCH BIT(10) struct reset_controller_dev *rcdev; unsigned int *resets; unsigned int reset_count; --=20 2.43.0 From nobody Mon Jun 8 06:39:48 2026 Received: from outbound6.mail.transip.nl (outbound6.mail.transip.nl [136.144.136.128]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A323B376497; Sat, 6 Jun 2026 12:34:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=136.144.136.128 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780749280; cv=none; b=msUD4/bPkZtAbMgv/mDAr1Rls7d/UT6P4T5ChIDIgIusCoHOWG5ZGedXlzhtob+WuDeQGxns9oGnZwzNXVEX12W0T/3ck088Xc9ABOvVmeStLe5CO3QQrnISRWGB+RLR0vEUdJumie2dd3BZN+NdgDdK+dvtXm0NXBkvHc7Rocc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780749280; c=relaxed/simple; bh=LzvzZKmNu1BSB5msNlu9m3rhENlsdqQSZlFNw6binx4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qZ+ObhNV1Z0UK5LPUayxDEYgipBdM3cymJN/GpL4n8SaQFiTSvhw4xnqmEY4s3zYBIjPDwkmRFm798857s/nZw+mOFDgAEjBpI4X3CYsMudPGhXkgwaYhLDazALGbzO215vX/No9qc3O9v5b9qDJTJosBoT0xOmLbZqJcZEIeQE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org; spf=pass smtp.mailfrom=herrie.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b=Onljp8uL; arc=none smtp.client-ip=136.144.136.128 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=herrie.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=herrie.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=herrie.org header.i=@herrie.org header.b="Onljp8uL" Received: from submission1.mail.transip.nl (unknown [10.100.4.70]) by outbound6.mail.transip.nl (Postfix) with ESMTP id 4gXd6D29rczwLHKF; Sat, 6 Jun 2026 14:34:28 +0200 (CEST) Received: from [127.0.1.1] (180-93-184-31.ftth.glasoperator.nl [31.184.93.180]) by submission1.mail.transip.nl (Postfix) with ESMTPA id 4gXd6C5D0HzJjhYL; Sat, 6 Jun 2026 14:34:27 +0200 (CEST) From: Herman van Hazendonk Date: Sat, 06 Jun 2026 14:34:27 +0200 Subject: [PATCH v2 2/2] clk: qcom: gdsc: add RPM_ALWAYS_ON flag Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-2-187a32d2f015@herrie.org> References: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-0-187a32d2f015@herrie.org> In-Reply-To: <20260606-submit-clk-gdsc-msm8x60-legacy-v2-0-187a32d2f015@herrie.org> To: Bjorn Andersson , Konrad Dybcio , Stephen Boyd , Michael Turquette Cc: linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org, linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, Herman van Hazendonk X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780749266; l=4171; i=github.com@herrie.org; s=20240417; h=from:subject:message-id; bh=LzvzZKmNu1BSB5msNlu9m3rhENlsdqQSZlFNw6binx4=; b=Ssu9KUA/LX8wD8/C3T6ykEBpfByQKBjqjdM2WK9ppfDvJRI40d9Q7jLDH/HW5Jg0YZRH8ejcg Dr1o5/XfaiIDS59eUyEwsA4y1HY53Wa8zYuhJzPdWSLznrcPTNFhDnT X-Developer-Key: i=github.com@herrie.org; a=ed25519; pk=YYxdq8fb5O9vhkW3n2dCH044FPZZO5718v/du7fRhFw= X-Scanned-By: ClueGetter at submission1.mail.transip.nl DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=transip-a; d=herrie.org; t=1780749267; h=from:subject:to:cc: references:in-reply-to:date:mime-version:content-type; bh=Q0oQqrHUfpO+gdshMA+ahrF6Af1d6WXHr7563kjZMYU=; b=Onljp8uL+x9Rt+NkN8iixcVKSTqNNxr8vD7bEgAOcdWJefYh4mdWi9X8Ii3zTrnH03+0Uq 2aQMHk8FVst+3NfbBwy3DDG1l5fW/xt6phMBDYIGSmzWkXX1TYzPti1waYdZcQcZsqMqlT vrTULDmshqlgPI1KUDfETTSQtMZiCvMUjEhGU9UTdKCDQT63TIxXAt2RzVRlk1voBhT+0c my7S8fZ/JZvHXfvFv9+/BjIvY0e3ZOV3I30o2xiUV2UgoMqHLQL7c93U8WXZKpa73NRmQ+ zFSaeDJnpLBRiV6+t4kipawvTVkCcdFrYZtlQsjGkVTeGH8EGi2dEY/2MieuiQ== X-Report-Abuse-To: abuse@transip.nl Some power domains need to stay powered across runtime PM even though their clocks may still gate, and only collapse on full system suspend. Add an RPM_ALWAYS_ON flag that maps to the existing GENPD_FLAG_RPM_ALWAYS_ON on the underlying generic_pm_domain. This is distinct from the existing ALWAYS_ON flag (which keeps the domain permanently enabled and prevents collapse even during system suspend) and from leaving the flag unset (which allows the domain to collapse on every runtime-idle transition). GENPD_FLAG_RPM_ALWAYS_ON, like GENPD_FLAG_ALWAYS_ON, requires the underlying genpd to be in the ON state at pm_genpd_init() time -- the framework rejects registration otherwise. Fold RPM_ALWAYS_ON into the gdsc_init() block that already force-enables ALWAYS_ON GDSCs found powered down at sync time so the flag combination is honoured consistently. The first user is the upcoming MSM8x60 MMCC driver, which needs RPM_ALWAYS_ON on the a2xx (Adreno 220) GFX3D footswitch: cold-cycling the GPU rail on every runtime idle forces an a2xx_hw_init microcode reload whose MMIO burst can stall the shared MMSS AXI fabric when it coincides with an MDP display client-switch underrun, hard-hanging the SoC. Letting the rail stay up during runtime PM (clocks still gate, idle power is still saved) and only collapsing on system suspend avoids the corner case while still allowing full power-down during deep sleep. Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5 Signed-off-by: Herman van Hazendonk --- drivers/clk/qcom/gdsc.c | 21 ++++++++++++++++++--- drivers/clk/qcom/gdsc.h | 12 ++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c index 1f80bebe88c7..0622bb23d17b 100644 --- a/drivers/clk/qcom/gdsc.c +++ b/drivers/clk/qcom/gdsc.c @@ -777,9 +777,22 @@ static int gdsc_init(struct gdsc *sc) goto err_disable_supply; } =20 - } else if (sc->flags & ALWAYS_ON) { - /* If ALWAYS_ON GDSCs are not ON, turn them ON */ - gdsc_enable(&sc->pd); + } else if (sc->flags & (ALWAYS_ON | RPM_ALWAYS_ON)) { + /* + * Both GENPD_FLAG_ALWAYS_ON and GENPD_FLAG_RPM_ALWAYS_ON + * require the domain to be ON at pm_genpd_init() time -- + * the framework rejects registration otherwise. Bring up + * any such GDSC that is currently off so the genpd flags + * we set below match the silicon state. + * + * Propagate the gdsc_enable() return so a failure here does + * not silently set on=3Dtrue and leak a vote through the + * err_disable_supply path (which would unwind a vote that + * was never actually taken). + */ + ret =3D gdsc_enable(&sc->pd); + if (ret) + return ret; on =3D true; } =20 @@ -790,6 +803,8 @@ static int gdsc_init(struct gdsc *sc) =20 if (sc->flags & ALWAYS_ON) sc->pd.flags |=3D GENPD_FLAG_ALWAYS_ON; + if (sc->flags & RPM_ALWAYS_ON) + sc->pd.flags |=3D GENPD_FLAG_RPM_ALWAYS_ON; if (!sc->pd.power_off) sc->pd.power_off =3D gdsc_disable; if (!sc->pd.power_on) diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h index 13ca09f93a01..27acf20e8d68 100644 --- a/drivers/clk/qcom/gdsc.h +++ b/drivers/clk/qcom/gdsc.h @@ -75,6 +75,18 @@ struct gdsc { * transition completed after a fixed delay rather than polling status. */ #define LEGACY_FOOTSWITCH BIT(10) +/* + * Keep the domain powered across runtime PM (its clocks may still gate via + * the clock framework) and only allow it to power-collapse on system + * suspend. Maps to GENPD_FLAG_RPM_ALWAYS_ON on the underlying genpd. Usef= ul + * for blocks whose cold-start sequence is expensive enough that runtime + * power cycling causes user-visible latency or hardware corner-case bugs = -- + * e.g. the MSM8x60 a2xx (Adreno 220) graphics footswitch, whose first + * power-up after collapse forces a full microcode reload that can stall + * the shared MMSS AXI fabric when it coincides with an MDP display + * underrun. + */ +#define RPM_ALWAYS_ON BIT(11) struct reset_controller_dev *rcdev; unsigned int *resets; unsigned int reset_count; --=20 2.43.0