summaryrefslogtreecommitdiffstats
path: root/drivers/clk/sprd/pll.c
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /drivers/clk/sprd/pll.c
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'drivers/clk/sprd/pll.c')
-rw-r--r--drivers/clk/sprd/pll.c263
1 files changed, 263 insertions, 0 deletions
diff --git a/drivers/clk/sprd/pll.c b/drivers/clk/sprd/pll.c
new file mode 100644
index 000000000..4aec30777
--- /dev/null
+++ b/drivers/clk/sprd/pll.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Spreadtrum pll clock driver
+//
+// Copyright (C) 2015~2017 Spreadtrum, Inc.
+// Author: Chunyan Zhang <chunyan.zhang@spreadtrum.com>
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "pll.h"
+
+#define CLK_PLL_1M 1000000
+#define CLK_PLL_10M (CLK_PLL_1M * 10)
+
+#define pindex(pll, member) \
+ (pll->factors[member].shift / (8 * sizeof(pll->regs_num)))
+
+#define pshift(pll, member) \
+ (pll->factors[member].shift % (8 * sizeof(pll->regs_num)))
+
+#define pwidth(pll, member) \
+ pll->factors[member].width
+
+#define pmask(pll, member) \
+ ((pwidth(pll, member)) ? \
+ GENMASK(pwidth(pll, member) + pshift(pll, member) - 1, \
+ pshift(pll, member)) : 0)
+
+#define pinternal(pll, cfg, member) \
+ (cfg[pindex(pll, member)] & pmask(pll, member))
+
+#define pinternal_val(pll, cfg, member) \
+ (pinternal(pll, cfg, member) >> pshift(pll, member))
+
+static inline unsigned int
+sprd_pll_read(const struct sprd_pll *pll, u8 index)
+{
+ const struct sprd_clk_common *common = &pll->common;
+ unsigned int val = 0;
+
+ if (WARN_ON(index >= pll->regs_num))
+ return 0;
+
+ regmap_read(common->regmap, common->reg + index * 4, &val);
+
+ return val;
+}
+
+static inline void
+sprd_pll_write(const struct sprd_pll *pll, u8 index,
+ u32 msk, u32 val)
+{
+ const struct sprd_clk_common *common = &pll->common;
+ unsigned int offset, reg;
+ int ret = 0;
+
+ if (WARN_ON(index >= pll->regs_num))
+ return;
+
+ offset = common->reg + index * 4;
+ ret = regmap_read(common->regmap, offset, &reg);
+ if (!ret)
+ regmap_write(common->regmap, offset, (reg & ~msk) | val);
+}
+
+static unsigned long pll_get_refin(const struct sprd_pll *pll)
+{
+ u32 shift, mask, index, refin_id = 3;
+ const unsigned long refin[4] = { 2, 4, 13, 26 };
+
+ if (pwidth(pll, PLL_REFIN)) {
+ index = pindex(pll, PLL_REFIN);
+ shift = pshift(pll, PLL_REFIN);
+ mask = pmask(pll, PLL_REFIN);
+ refin_id = (sprd_pll_read(pll, index) & mask) >> shift;
+ if (refin_id > 3)
+ refin_id = 3;
+ }
+
+ return refin[refin_id];
+}
+
+static u32 pll_get_ibias(u64 rate, const u64 *table)
+{
+ u32 i, num = table[0];
+
+ /* table[0] indicates the number of items in this table */
+ for (i = 0; i < num; i++)
+ if (rate <= table[i + 1])
+ break;
+
+ return i == num ? num - 1 : i;
+}
+
+static unsigned long _sprd_pll_recalc_rate(const struct sprd_pll *pll,
+ unsigned long parent_rate)
+{
+ u32 *cfg;
+ u32 i, mask, regs_num = pll->regs_num;
+ unsigned long rate, nint, kint = 0;
+ u64 refin;
+ u16 k1, k2;
+
+ cfg = kcalloc(regs_num, sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return parent_rate;
+
+ for (i = 0; i < regs_num; i++)
+ cfg[i] = sprd_pll_read(pll, i);
+
+ refin = pll_get_refin(pll);
+
+ if (pinternal(pll, cfg, PLL_PREDIV))
+ refin = refin * 2;
+
+ if (pwidth(pll, PLL_POSTDIV) &&
+ ((pll->fflag == 1 && pinternal(pll, cfg, PLL_POSTDIV)) ||
+ (!pll->fflag && !pinternal(pll, cfg, PLL_POSTDIV))))
+ refin = refin / 2;
+
+ if (!pinternal(pll, cfg, PLL_DIV_S)) {
+ rate = refin * pinternal_val(pll, cfg, PLL_N) * CLK_PLL_10M;
+ } else {
+ nint = pinternal_val(pll, cfg, PLL_NINT);
+ if (pinternal(pll, cfg, PLL_SDM_EN))
+ kint = pinternal_val(pll, cfg, PLL_KINT);
+
+ mask = pmask(pll, PLL_KINT);
+
+ k1 = pll->k1;
+ k2 = pll->k2;
+ rate = DIV_ROUND_CLOSEST_ULL(refin * kint * k1,
+ ((mask >> __ffs(mask)) + 1)) *
+ k2 + refin * nint * CLK_PLL_1M;
+ }
+
+ kfree(cfg);
+ return rate;
+}
+
+#define SPRD_PLL_WRITE_CHECK(pll, i, mask, val) \
+ (((sprd_pll_read(pll, i) & mask) == val) ? 0 : (-EFAULT))
+
+static int _sprd_pll_set_rate(const struct sprd_pll *pll,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct reg_cfg *cfg;
+ int ret = 0;
+ u32 mask, shift, width, ibias_val, index;
+ u32 regs_num = pll->regs_num, i = 0;
+ unsigned long kint, nint;
+ u64 tmp, refin, fvco = rate;
+
+ cfg = kzalloc_objs(*cfg, regs_num);
+ if (!cfg)
+ return -ENOMEM;
+
+ refin = pll_get_refin(pll);
+
+ mask = pmask(pll, PLL_PREDIV);
+ index = pindex(pll, PLL_PREDIV);
+ width = pwidth(pll, PLL_PREDIV);
+ if (width && (sprd_pll_read(pll, index) & mask))
+ refin = refin * 2;
+
+ mask = pmask(pll, PLL_POSTDIV);
+ index = pindex(pll, PLL_POSTDIV);
+ width = pwidth(pll, PLL_POSTDIV);
+ cfg[index].msk = mask;
+ if (width && ((pll->fflag == 1 && fvco <= pll->fvco) ||
+ (pll->fflag == 0 && fvco > pll->fvco)))
+ cfg[index].val |= mask;
+
+ if (width && fvco <= pll->fvco)
+ fvco = fvco * 2;
+
+ mask = pmask(pll, PLL_DIV_S);
+ index = pindex(pll, PLL_DIV_S);
+ cfg[index].val |= mask;
+ cfg[index].msk |= mask;
+
+ mask = pmask(pll, PLL_SDM_EN);
+ index = pindex(pll, PLL_SDM_EN);
+ cfg[index].val |= mask;
+ cfg[index].msk |= mask;
+
+ nint = do_div(fvco, refin * CLK_PLL_1M);
+ mask = pmask(pll, PLL_NINT);
+ index = pindex(pll, PLL_NINT);
+ shift = pshift(pll, PLL_NINT);
+ cfg[index].val |= (nint << shift) & mask;
+ cfg[index].msk |= mask;
+
+ mask = pmask(pll, PLL_KINT);
+ index = pindex(pll, PLL_KINT);
+ width = pwidth(pll, PLL_KINT);
+ shift = pshift(pll, PLL_KINT);
+ tmp = fvco - refin * nint * CLK_PLL_1M;
+ tmp = do_div(tmp, 10000) * ((mask >> shift) + 1);
+ kint = DIV_ROUND_CLOSEST_ULL(tmp, refin * 100);
+ cfg[index].val |= (kint << shift) & mask;
+ cfg[index].msk |= mask;
+
+ ibias_val = pll_get_ibias(fvco, pll->itable);
+
+ mask = pmask(pll, PLL_IBIAS);
+ index = pindex(pll, PLL_IBIAS);
+ shift = pshift(pll, PLL_IBIAS);
+ cfg[index].val |= ibias_val << shift & mask;
+ cfg[index].msk |= mask;
+
+ for (i = 0; i < regs_num; i++) {
+ if (cfg[i].msk) {
+ sprd_pll_write(pll, i, cfg[i].msk, cfg[i].val);
+ ret |= SPRD_PLL_WRITE_CHECK(pll, i, cfg[i].msk,
+ cfg[i].val);
+ }
+ }
+
+ if (!ret)
+ udelay(pll->udelay);
+
+ kfree(cfg);
+ return ret;
+}
+
+static unsigned long sprd_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sprd_pll *pll = hw_to_sprd_pll(hw);
+
+ return _sprd_pll_recalc_rate(pll, parent_rate);
+}
+
+static int sprd_pll_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sprd_pll *pll = hw_to_sprd_pll(hw);
+
+ return _sprd_pll_set_rate(pll, rate, parent_rate);
+}
+
+static int sprd_pll_clk_prepare(struct clk_hw *hw)
+{
+ struct sprd_pll *pll = hw_to_sprd_pll(hw);
+
+ udelay(pll->udelay);
+
+ return 0;
+}
+
+const struct clk_ops sprd_pll_ops = {
+ .prepare = sprd_pll_clk_prepare,
+ .recalc_rate = sprd_pll_recalc_rate,
+ .determine_rate = clk_determine_rate_noop,
+ .set_rate = sprd_pll_set_rate,
+};
+EXPORT_SYMBOL_GPL(sprd_pll_ops);