From: Sasha Levin <sashal@kernel.org>
To: patches@lists.linux.dev, stable@vger.kernel.org
Cc: Nicolas Ferre <nicolas.ferre@microchip.com>,
Ryan Wanner <ryan.wanner@microchip.com>,
Claudiu Beznea <claudiu.beznea@tuxon.dev>,
Sasha Levin <sashal@kernel.org>,
alexandre.belloni@bootlin.com, alexandre.f.demers@gmail.com,
cristian.birsan@microchip.com, andrei.simion@microchip.com,
bmasney@redhat.com, Ryan.Wanner@microchip.com,
linux-arm-kernel@lists.infradead.org
Subject: [PATCH AUTOSEL 6.17-6.1] clk: at91: clk-sam9x60-pll: force write to PLL_UPDT register
Date: Sun, 26 Oct 2025 10:49:10 -0400 [thread overview]
Message-ID: <20251026144958.26750-32-sashal@kernel.org> (raw)
In-Reply-To: <20251026144958.26750-1-sashal@kernel.org>
From: Nicolas Ferre <nicolas.ferre@microchip.com>
[ Upstream commit af98caeaa7b6ad11eb7b7c8bfaddc769df2889f3 ]
This register is important for sequencing the commands to PLLs, so
actually write the update bits with regmap_write_bits() instead of
relying on a read/modify/write regmap command that could skip the actual
hardware write if the value is identical to the one read.
It's changed when modification is needed to the PLL, when
read-only operation is done, we could keep the call to
regmap_update_bits().
Add a comment to the sam9x60_div_pll_set_div() function that uses this
PLL_UPDT register so that it's used consistently, according to the
product's datasheet.
Signed-off-by: Nicolas Ferre <nicolas.ferre@microchip.com>
Tested-by: Ryan Wanner <ryan.wanner@microchip.com> # on sama7d65 and sam9x75
Link: https://lore.kernel.org/r/20250827150811.82496-1-nicolas.ferre@microchip.com
[claudiu.beznea: fix "Alignment should match open parenthesis"
checkpatch.pl check]
Signed-off-by: Claudiu Beznea <claudiu.beznea@tuxon.dev>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
YES
- `regmap_update_bits()` skips the hardware write when the target value
matches the current value, so repeated programming of the PLL update
register could silently do nothing. The replacement with
`regmap_write_bits()`—which forces the transaction by passing
`force=true` down to `_regmap_update_bits()` (see
`include/linux/regmap.h:1340` and
`drivers/base/regmap/regmap.c:3247`)—guarantees every update command
actually reaches the PMC.
- The fix touches every path that modifies PLL state: the PLL enable
sequence now forces the ID latch and both update pulses in
`sam9x60_frac_pll_set()` (`drivers/clk/at91/clk-sam9x60-pll.c:96`,
`:128`, `:136`), and the disable path does the same (`:164-175`).
Without these forced writes, the “apply changes” strobes could be
dropped, leaving MUL/FRAC reprogramming or ENPLL clears
unapplied—manifesting as PLLs that refuse to retune or power down.
- Divider programming follows the same requirement:
`sam9x60_div_pll_set()`, `_unprepare()`, `_set_rate_chg()`, and the
notifier all now force the ID and UPDATE pulse (`drivers/clk/at91/clk-
sam9x60-pll.c:365-413`, `:528-599`). This prevents cases where DVFS
notifier transitions or runtime rate changes fail because the write
was skipped, which can lead to over-clocking or clocks stuck at stale
divisors.
- Read-only users are intentionally left on `regmap_update_bits()` (e.g.
`sam9x60_div_pll_is_prepared()` at `drivers/clk/at91/clk-
sam9x60-pll.c:427-434`), and the new comment at `:343-348` documents
the datasheet requirement that the correct PLL ID already be latched
before issuing the forced update. That keeps behaviour consistent and
avoids accidental misuse.
- The change is localized to the AT91 SAM9x60 PLL driver, introduces no
new APIs, and simply guarantees the hardware sequencing works as
documented; it has been validated on Sama7D65/Sam9x75 hardware per the
Tested-by tag. The bug being fixed—lost update strobes leading to PLLs
that won’t reliably enable/disable—is severe for users, while the
regression risk from issuing guaranteed writes is minimal.
drivers/clk/at91/clk-sam9x60-pll.c | 75 ++++++++++++++++--------------
1 file changed, 39 insertions(+), 36 deletions(-)
diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
index cefd9948e1039..a035dc15454b0 100644
--- a/drivers/clk/at91/clk-sam9x60-pll.c
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -93,8 +93,8 @@ static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
spin_lock_irqsave(core->lock, flags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
@@ -128,17 +128,17 @@ static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
udelay(10);
}
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
while (!sam9x60_pll_ready(regmap, core->id))
cpu_relax();
@@ -164,8 +164,8 @@ static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
spin_lock_irqsave(core->lock, flags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, AT91_PMC_PLL_CTRL0_ENPLL, 0);
@@ -173,9 +173,9 @@ static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
regmap_update_bits(regmap, AT91_PMC_PLL_ACR,
AT91_PMC_PLL_ACR_UTMIBG | AT91_PMC_PLL_ACR_UTMIVR, 0);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
spin_unlock_irqrestore(core->lock, flags);
}
@@ -262,8 +262,8 @@ static int sam9x60_frac_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
spin_lock_irqsave(core->lock, irqflags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
- core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
@@ -275,18 +275,18 @@ static int sam9x60_frac_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
(frac->mul << core->layout->mul_shift) |
(frac->frac << core->layout->frac_shift));
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
AT91_PMC_PLL_CTRL0_ENLOCK |
AT91_PMC_PLL_CTRL0_ENPLL);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
while (!sam9x60_pll_ready(regmap, core->id))
cpu_relax();
@@ -338,7 +338,10 @@ static const struct clk_ops sam9x60_frac_pll_ops_chg = {
.restore_context = sam9x60_frac_pll_restore_context,
};
-/* This function should be called with spinlock acquired. */
+/* This function should be called with spinlock acquired.
+ * Warning: this function must be called only if the same PLL ID was set in
+ * PLL_UPDT register previously.
+ */
static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
bool enable)
{
@@ -350,9 +353,9 @@ static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
core->layout->div_mask | ena_msk,
(div << core->layout->div_shift) | ena_val);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
while (!sam9x60_pll_ready(regmap, core->id))
cpu_relax();
@@ -366,8 +369,8 @@ static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
unsigned int val, cdiv;
spin_lock_irqsave(core->lock, flags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
@@ -398,15 +401,15 @@ static void sam9x60_div_pll_unprepare(struct clk_hw *hw)
spin_lock_irqsave(core->lock, flags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
core->layout->endiv_mask, 0);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
- AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
- AT91_PMC_PLL_UPDT_UPDATE | core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
spin_unlock_irqrestore(core->lock, flags);
}
@@ -518,8 +521,8 @@ static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;
spin_lock_irqsave(core->lock, irqflags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
- core->id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core->id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
@@ -574,8 +577,8 @@ static int sam9x60_div_pll_notifier_fn(struct notifier_block *notifier,
div->div = div->safe_div;
spin_lock_irqsave(core.lock, irqflags);
- regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
- core.id);
+ regmap_write_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core.id);
regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
cdiv = (val & core.layout->div_mask) >> core.layout->div_shift;
--
2.51.0
next prev parent reply other threads:[~2025-10-26 14:51 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-26 14:48 [PATCH AUTOSEL 6.17-5.4] ACPI: property: Return present device nodes only on fwnode interface Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.4] ceph: add checking of wait_for_completion_killable() return value Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.4] 9p: sysfs_init: don't hardcode error to ENOMEM Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.10] um: Fix help message for ssl-non-raw Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] clk: thead: th1520-ap: set all AXI clocks to CLK_IS_CRITICAL Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-6.1] NTB: epf: Allow arbitrary BAR mapping Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] rtc: zynqmp: Restore alarm functionality after kexec transition Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] hyperv: Add missing field to hv_output_map_device_interrupt Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.4] fbdev: Add bounds checking in bit_putcs to fix vmalloc-out-of-bounds Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] fbdev: core: Fix ubsan warning in pixel_to_pat Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.10] ASoC: meson: aiu-encoder-i2s: fix bit clock polarity Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.4] fs/hpfs: Fix error code for new_inode() failure in mkdir/create/mknod/symlink Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] drm/amdgpu: Report individual reset error Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.15] clk: ti: am33xx: keep WKUP_DEBUGSS_CLKCTRL enabled Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17] clk: at91: add ACR in all PLL settings Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-6.12] clk: scmi: Add duty cycle ops only when duty cycle is supported Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.10] ARM: at91: pm: save and restore ACR during PLL disable/enable Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-6.6] rtc: pcf2127: fix watchdog interrupt mask on pcf2131 Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.15] clk: at91: clk-master: Add check for divide by 3 Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-5.15] rtc: pcf2127: clear minute/second interrupt Sasha Levin
2025-10-26 14:48 ` [PATCH AUTOSEL 6.17-6.12] clk: at91: sam9x7: Add peripheral clock id for pmecc Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-5.4] 9p: fix /sys/fs/9p/caches overwriting itself Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] 9p/trans_fd: p9_fd_request: kick rx thread if EPOLLIN Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17] clk: samsung: exynos990: Add missing USB clock registers to HSI0 Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17] clocksource: hyper-v: Skip unnecessary checks for the root partition Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] ceph: fix multifs mds auth caps issue Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] LoongArch: Handle new atomic instructions for probes Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.6] ceph: refactor wake_up_bit() pattern of calling Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] drm/amdkfd: Fix mmap write lock not release Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] ceph: fix potential race condition in ceph_ioctl_lazyio() Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] clk: qcom: gcc-ipq6018: rework nss_port5 clock to multiple conf Sasha Levin
2025-10-26 14:49 ` Sasha Levin [this message]
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-5.4] tools bitmap: Add missing asm-generic/bitsperlong.h include Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17] ALSA: hda/realtek: Add quirk for ASUS ROG Zephyrus Duo Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.1] kbuild: uapi: Strip comments before size type check Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.1] tools: lib: thermal: don't preserve owner in install Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.1] scsi: ufs: core: Include UTP error in INT_FATAL_ERRORS Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.1] clk: sunxi-ng: sun6i-rtc: Add A523 specifics Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] clk: scmi: migrate round_rate() to determine_rate() Sasha Levin
2025-10-26 23:16 ` Brian Masney
2025-10-28 17:47 ` Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] clk: clocking-wizard: Fix output clock register offset for Versal platforms Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17] ASoC: rt722: add settings for rt722VB Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-5.15] cpufreq: tegra186: Initialize all cores to max frequencies Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.1] tools: lib: thermal: use pkg-config to locate libnl3 Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17] clk: renesas: rzv2h: Re-assert reset on deassert timeout Sasha Levin
2025-10-26 14:49 ` [PATCH AUTOSEL 6.17-6.12] net: wwan: t7xx: add support for HP DRMR-H01 Sasha Levin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251026144958.26750-32-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=alexandre.belloni@bootlin.com \
--cc=alexandre.f.demers@gmail.com \
--cc=andrei.simion@microchip.com \
--cc=bmasney@redhat.com \
--cc=claudiu.beznea@tuxon.dev \
--cc=cristian.birsan@microchip.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=nicolas.ferre@microchip.com \
--cc=patches@lists.linux.dev \
--cc=ryan.wanner@microchip.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).