* [PATCH 1/2] clk: qcom: gdsc: add LEGACY_FOOTSWITCH support for MSM8x60
[not found] <20260602050840.435933-1-github.com@herrie.org>
@ 2026-06-02 5:08 ` Herman van Hazendonk
2026-06-02 5:08 ` [PATCH 2/2] clk: qcom: gdsc: add RPM_ALWAYS_ON flag Herman van Hazendonk
1 sibling, 0 replies; 2+ messages in thread
From: Herman van Hazendonk @ 2026-06-02 5:08 UTC (permalink / raw)
To: sboyd
Cc: Herman van Hazendonk, Bjorn Andersson, Michael Turquette,
linux-arm-msm, linux-clk, linux-kernel
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:
enable: assert resets -> set ENABLE -> 2us settle ->
deassert resets -> clear CLAMP -> 5us settle
disable: assert resets -> set CLAMP -> clear ENABLE
If the ENABLE write fails after asserting resets, deassert them
again before returning so the hardware block is not left stuck
in reset for the remainder of the system's lifetime.
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.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/clk/qcom/gdsc.c | 147 ++++++++++++++++++++++++++++++++++++++++
drivers/clk/qcom/gdsc.h | 7 ++
2 files changed, 154 insertions(+)
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index 95aa07120245..64a9b315a9c2 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)
+/* 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,23 @@ static int gdsc_check_status(struct gdsc *sc, enum gdsc_status status)
u32 val;
int ret;
+ /*
+ * 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 = regmap_read(sc->regmap, sc->gdscr, &val);
+ if (ret)
+ return ret;
+ switch (status) {
+ case GDSC_ON:
+ return !!(val & LEGACY_FS_ENABLE_MASK);
+ case GDSC_OFF:
+ return !(val & LEGACY_FS_ENABLE_MASK);
+ }
+ return -EINVAL;
+ }
+
if (sc->flags & POLL_CFG_GDSCR)
reg = sc->gdscr + CFG_GDSCR_OFFSET;
else if (sc->gds_hw_ctrl)
@@ -121,6 +143,18 @@ static int gdsc_update_collapse_bit(struct gdsc *sc, bool val)
u32 reg, mask;
int ret;
+ /*
+ * 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 = sc->gdscr;
+ mask = LEGACY_FS_ENABLE_MASK;
+ return regmap_update_bits(sc->regmap, reg, mask,
+ val ? 0 : mask);
+ }
+
if (sc->collapse_mask) {
reg = sc->collapse_ctrl;
mask = sc->collapse_mask;
@@ -240,6 +274,23 @@ static inline void gdsc_assert_clamp_io(struct gdsc *sc)
GMEM_CLAMP_IO_MASK, 1);
}
+/*
+ * 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,
@@ -264,6 +315,55 @@ static int gdsc_enable(struct generic_pm_domain *domain)
if (sc->pwrsts == PWRSTS_ON)
return gdsc_deassert_reset(sc);
+ /*
+ * Legacy MSM8x60 footswitch enable sequence:
+ * 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->flags & SW_RESET)
+ gdsc_assert_reset(sc);
+
+ ret = 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.
+ */
+ if (sc->flags & SW_RESET)
+ gdsc_deassert_reset(sc);
+ return ret;
+ }
+
+ udelay(2);
+
+ if (sc->flags & SW_RESET)
+ gdsc_deassert_reset(sc);
+
+ ret = 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.
+ */
+ gdsc_update_collapse_bit(sc, true);
+ return ret;
+ }
+
+ udelay(5);
+
+ return 0;
+ }
+
if (sc->flags & SW_RESET) {
gdsc_assert_reset(sc);
udelay(1);
@@ -322,6 +422,31 @@ static int gdsc_disable(struct generic_pm_domain *domain)
if (sc->pwrsts == PWRSTS_ON)
return gdsc_assert_reset(sc);
+ /*
+ * 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
+ */
+ if (sc->flags & LEGACY_FOOTSWITCH) {
+ if (sc->flags & SW_RESET)
+ gdsc_assert_reset(sc);
+
+ ret = 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;
+ }
+
+ return gdsc_update_collapse_bit(sc, true);
+ }
+
/* Turn off HW trigger mode if supported */
if (sc->flags & HW_CTRL) {
ret = gdsc_hwctrl(sc, false);
@@ -405,6 +530,27 @@ static int gdsc_init(struct gdsc *sc)
u32 mask, val;
int 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 = regmap_update_bits(sc->regmap, sc->gdscr,
+ LEGACY_FS_RETENTION_MASK, 0);
+ if (ret)
+ return ret;
+ goto skip_wait_config;
+ }
+
/*
* Disable HW trigger: collapse/restore occur based on registers writes.
* Disable SW override: Use hardware state-machine for sequencing.
@@ -428,6 +574,7 @@ static int gdsc_init(struct gdsc *sc)
if (ret)
return ret;
+skip_wait_config:
/* Force gdsc ON if only ON state is supported */
if (sc->pwrsts == PWRSTS_ON) {
ret = gdsc_toggle_logic(sc, GDSC_ON, false);
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 layout
+ * 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;
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [PATCH 2/2] clk: qcom: gdsc: add RPM_ALWAYS_ON flag
[not found] <20260602050840.435933-1-github.com@herrie.org>
2026-06-02 5:08 ` [PATCH 1/2] clk: qcom: gdsc: add LEGACY_FOOTSWITCH support for MSM8x60 Herman van Hazendonk
@ 2026-06-02 5:08 ` Herman van Hazendonk
1 sibling, 0 replies; 2+ messages in thread
From: Herman van Hazendonk @ 2026-06-02 5:08 UTC (permalink / raw)
To: sboyd
Cc: Herman van Hazendonk, Bjorn Andersson, Michael Turquette,
linux-arm-msm, linux-clk, linux-kernel
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).
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.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/clk/qcom/gdsc.c | 2 ++
drivers/clk/qcom/gdsc.h | 12 ++++++++++++
2 files changed, 14 insertions(+)
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index 64a9b315a9c2..e319b905c31b 100644
--- a/drivers/clk/qcom/gdsc.c
+++ b/drivers/clk/qcom/gdsc.c
@@ -632,6 +632,8 @@ static int gdsc_init(struct gdsc *sc)
if (sc->flags & ALWAYS_ON)
sc->pd.flags |= GENPD_FLAG_ALWAYS_ON;
+ if (sc->flags & RPM_ALWAYS_ON)
+ sc->pd.flags |= GENPD_FLAG_RPM_ALWAYS_ON;
if (!sc->pd.power_off)
sc->pd.power_off = 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. Useful
+ * 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;
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-06-02 5:08 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260602050840.435933-1-github.com@herrie.org>
2026-06-02 5:08 ` [PATCH 1/2] clk: qcom: gdsc: add LEGACY_FOOTSWITCH support for MSM8x60 Herman van Hazendonk
2026-06-02 5:08 ` [PATCH 2/2] clk: qcom: gdsc: add RPM_ALWAYS_ON flag Herman van Hazendonk
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox