Hi AngeloGioacchino,
Thanks for all the advices.
On Mon, 2022-06-13 at 17:43 +0800, AngeloGioacchino Del Regno wrote:
Il 12/06/22 15:54, Johnson Wang ha scritto:
Add frequency hopping support and spread spectrum clocking
control for MT8186.
Signed-off-by: Edward-JW Yang <edward-jw.yang@xxxxxxxxxxxx>
Signed-off-by: Johnson Wang <johnson.wang@xxxxxxxxxxxx>
Before going on with the review, there's one important consideration:
the Frequency Hopping control is related to PLLs only (so, no other clock
types get in the mix).
Checking the code, the *main* thing that we do here is initializing the
FHCTL by setting some registers, and we're performing the actual frequency
hopping operation in clk-pll, which is right but, at this point, I think
that the best way to proceed is to add the "FHCTL superpowers" to clk-pll
itself, instead of adding multiple new files and devicetree bindings that
are specific to the FHCTL itself.
This would mean that the `fh-id` and `perms` params that you're setting in
the devicetree get transferred to clk-mt8186 (and hardcoded there), as to
extend the PLL declarations to include these two: that will also simplify
the driver so that you won't have to match names here and there.
Just an example:
PLL(CLK_APMIXED_CCIPLL, "ccipll", 0x0224, 0x0230, 0,
PLL_AO, 0, 22, 0x0228, 24, 0, 0, 0, 0x0228, 2, FHCTL_PERM_DBG_DUMP),
Besides, there are another couple of reasons why you should do that instead,
of which:
- The devicetree should be "generic enough", we shall not see the direct value
to write to the registers in there (yet, perms assigns exactly that)
- These values won't change on a per-device basis, I believe? They're SoC-related,
not board-related, right?
In case they're board related (and/or related to TZ permissions), we can always add
a bool property to the apmixedsys to advertise that board X needs to use an
alternative permission (ex.: `mediatek,secure-fhctl`).
I think we should remain clk-fhctl files because FHCTL is a independent HW and is
not a necessary component of clk-pll.
Frequency hopping function from FHCTL is not used to replace original flow of
set_rate in clk-pll. They are two different ways to change PLL's frequency. The
current set_rate method in clk-pll changes PLL register setting directly. Another
way uses FHCTL to change PLL rate.
We will set some PLL's frequency be controlled
by clk-pll and some are controlled by FHCTL.
And use `perms` param to decide
whether a PLL is using FHCTL to change its frequency.
FHCTL has another function called SSC(spread spectrum clocking) which is used to
solve PLL de-sense problem. De-sense problem is board-related so we introduce a
`ssc-rate` param in the devicetree to decide whether SSC is enabled and how many
rate should be set. Mixing SSC function into clk-pll may cause clk-pll more
complex.
In any case, to speed up development (I believe that transferring this in clk-pll
means that the code will still be more or less the same), I've performed a review
on the code; check below.
---
drivers/clk/mediatek/Kconfig | 8 +
drivers/clk/mediatek/Makefile | 2 +
drivers/clk/mediatek/clk-fhctl-ap.c | 347 ++++++++++++++++++++++++++
drivers/clk/mediatek/clk-fhctl-pll.c | 209 ++++++++++++++++
drivers/clk/mediatek/clk-fhctl-pll.h | 74 ++++++
drivers/clk/mediatek/clk-fhctl-util.h | 24 ++
drivers/clk/mediatek/clk-fhctl.c | 191 ++++++++++++++
drivers/clk/mediatek/clk-fhctl.h | 45 ++++
drivers/clk/mediatek/clk-pll.c | 5 +-
drivers/clk/mediatek/clk-pll.h | 5 +
10 files changed, 909 insertions(+), 1 deletion(-)
create mode 100644 drivers/clk/mediatek/clk-fhctl-ap.c
create mode 100644 drivers/clk/mediatek/clk-fhctl-pll.c
create mode 100644 drivers/clk/mediatek/clk-fhctl-pll.h
create mode 100644 drivers/clk/mediatek/clk-fhctl-util.h
create mode 100644 drivers/clk/mediatek/clk-fhctl.c
create mode 100644 drivers/clk/mediatek/clk-fhctl.h
diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig
index d5936cfb3bee..fd887c537a91 100644
--- a/drivers/clk/mediatek/Kconfig
+++ b/drivers/clk/mediatek/Kconfig
@@ -622,4 +622,12 @@ config COMMON_CLK_MT8516_AUDSYS
help
This driver supports MediaTek MT8516 audsys clocks.
+config COMMON_CLK_MTK_FREQ_HOPPING
+ tristate "MediaTek frequency hopping driver"
If this goes inside of clk-pll, this configuration option can be safely removed.
I think we should keep this for clk-fhctl* files.
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ select COMMON_CLK_MEDIATEK
+ help
+ This driver supports frequency hopping and spread spectrum clocking
+ control for some MediaTek SoCs.
+
endmenu
diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile
index caf2ce93d666..3c0e9bd3978b 100644
--- a/drivers/clk/mediatek/Makefile
+++ b/drivers/clk/mediatek/Makefile
@@ -99,3 +99,5 @@ obj-$(CONFIG_COMMON_CLK_MT8195) += clk-mt8195-apmixedsys.o clk-mt8195-topckgen.o
clk-mt8195-apusys_pll.o
obj-$(CONFIG_COMMON_CLK_MT8516) += clk-mt8516.o
obj-$(CONFIG_COMMON_CLK_MT8516_AUDSYS) += clk-mt8516-aud.o
+obj-$(CONFIG_COMMON_CLK_MTK_FREQ_HOPPING) += fhctl.o
+fhctl-objs += clk-fhctl.o clk-fhctl-ap.o clk-fhctl-pll.o
diff --git a/drivers/clk/mediatek/clk-fhctl-ap.c b/drivers/clk/mediatek/clk-fhctl-ap.c
new file mode 100644
index 000000000000..9e3226a9c1ca
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl-ap.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "clk-fhctl.h"
+#include "clk-fhctl-pll.h"
+#include "clk-fhctl-util.h"
+
+#define FHCTL_TARGET FHCTL_AP
+
+#define PERCENT_TO_DDSLMT(dds, percent_m10) \
+ ((((dds) * (percent_m10)) >> 5) / 100)
+
+struct fh_ap_match {
+ char *name;
+ struct fh_hdlr *hdlr;
+ int (*init)(struct pll_dts *array, struct fh_ap_match *match);
+};
+
+struct hdlr_data {
+ struct pll_dts *array;
+ struct fh_pll_domain *domain;
+ spinlock_t *lock;
+};
+
+static int fhctl_set_ssc_regs(struct fh_pll_regs *regs,
+ struct fh_pll_data *data,
+ int fh_id, int rate)
+{
+ unsigned int updnlmt_val;
+
+ if (rate > 0) {
+ fh_set_field(regs->reg_cfg, data->frddsx_en, 0);
+ fh_set_field(regs->reg_cfg, data->sfstrx_en, 0);
+ fh_set_field(regs->reg_cfg, data->fhctlx_en, 0);
Are all of these writes to be performed with a barrier?
Can't we use writel_relaxed() for some, with a "final" writel() where ordering
*really* matters?
Do this mean use writel_relaxed() on the first two and writel() on the thrid?
Also, at least these three field settings are common between (rate > 0) and
(rate <= 0), so they can go outside of the conditional.
OK, we will move them to outside of conditional. Thanks.
+
+ /* Set the relative parameter registers (dt/df/upbnd/downbnd) */
+ fh_set_field(regs->reg_cfg, data->msk_frddsx_dys, data->df_val);
+ fh_set_field(regs->reg_cfg, data->msk_frddsx_dts, data->dt_val);
+
+ writel((readl(regs->reg_con_pcw) & data->dds_mask) |
+ data->tgl_org, regs->reg_dds);
+
+ /* Calculate UPDNLMT */
+ updnlmt_val = PERCENT_TO_DDSLMT((readl(regs->reg_dds) &
+ data->dds_mask), rate) <<
+ data->updnlmt_shft;
+
+ writel(updnlmt_val, regs->reg_updnlmt);
+
+ fh_set_field(regs->reg_hp_en, BIT(fh_id), 1);
+
+ /* Enable SSC */
+ fh_set_field(regs->reg_cfg, data->frddsx_en, 1);
+ /* Enable Hopping control */
+ fh_set_field(regs->reg_cfg, data->fhctlx_en, 1);
+
+ } else {
+ fh_set_field(regs->reg_cfg, data->frddsx_en, 0);
+ fh_set_field(regs->reg_cfg, data->sfstrx_en, 0);
+ fh_set_field(regs->reg_cfg, data->fhctlx_en, 0);
+
+ /* Switch to APMIXEDSYS control */
+ fh_set_field(regs->reg_hp_en, BIT(fh_id), 0);
+
+ /* Wait for DDS to be stable */
+ udelay(30);
+ }
+
+ return 0;
+}
+
+static int hopping_hw_flow(void *priv_data, char *domain_name, int fh_id,
+ unsigned int new_dds, int postdiv)
+{
+ struct fh_pll_domain *domain;
+ struct fh_pll_regs *regs;
+ struct fh_pll_data *data;
+ unsigned int dds_mask;
+ unsigned int mon_dds = 0;
+ int ret = 0;
+ unsigned int con_pcw_tmp;
+ struct hdlr_data *d = (struct hdlr_data *)priv_data;
+ struct pll_dts *array = d->array;
+
+ domain = d->domain;
+ regs = &domain->regs[fh_id];
+ data = &domain->data[fh_id];
+ dds_mask = data->dds_mask;
Just perform these assignments in the variable declarations... with some
reordering as well, and drop the zero assignment to ret.
In few words:
struct hdlr_data *d = (struct hdlr_data *)priv_data;
struct fh_pll_domain *domain = d->domain;
struct fh_pll_regs *regs = &domain->regs[fh_id];
struct fh_pll_data *data = &domain->data[fh_id];
struct pll_dts *array = d->array;
u32 con_pcw_tmp, dds_mask;
u32 mon_dds = 0;
int ret;
This comment is valid for some other functions as well - I won't repeat
this for every instance... :-)
OK, we will merge them. Thanks.
+
+ if (array->ssc_rate)
+ fhctl_set_ssc_regs(regs, data, fh_id, 0);
+
+ writel((readl(regs->reg_con_pcw) & dds_mask) |
+ data->tgl_org, regs->reg_dds);
+
+ fh_set_field(regs->reg_cfg, data->sfstrx_en, 1);
+ fh_set_field(regs->reg_cfg, data->fhctlx_en, 1);
+ writel(data->slope0_value, regs->reg_slope0);
+ writel(data->slope1_value, regs->reg_slope1);
+
+ fh_set_field(regs->reg_hp_en, BIT(fh_id), 1);
+ writel((new_dds) | (data->dvfs_tri), regs->reg_dvfs);
+
+ /* Wait 1000 us until DDS stable */
+ ret = readl_poll_timeout_atomic(regs->reg_mon, mon_dds,
+ (mon_dds & dds_mask) == new_dds, 10, 1000);
Why are you writing to CON_PCW even when this returns en error?
Please add a comment explaining the reasons.
Oh, we will add a warning log and dump HW register when this returns an error
The reg_mon is a register reflects the current frequency rate. So, it's fine to
write the current rate back to CON_PCW. We will also add a comment on it. Thanks
+
+ con_pcw_tmp = readl(regs->reg_con_pcw) & (~dds_mask);
+ con_pcw_tmp = (con_pcw_tmp | (readl(regs->reg_mon) & dds_mask) |
+ data->pcwchg);
+
+ writel(con_pcw_tmp, regs->reg_con_pcw);
+
+ fh_set_field(regs->reg_hp_en, BIT(fh_id), 0);
+
+ if (array->ssc_rate)
+ fhctl_set_ssc_regs(regs, data, fh_id, array->ssc_rate);
+
+ return ret;
+}
+
+static unsigned int __get_postdiv(struct fh_pll_regs *regs,
+ struct fh_pll_data *data)
+{
+ unsigned int regval;
+
+ regval = (readl(regs->reg_con_postdiv) & data->postdiv_mask)
+ >> data->postdiv_offset;
+
+ return data->postdiv_table[regval];
Can we instead simply reuse `struct clk_div_table` from clk-provider.h?
"postdiv" is part of setting in PCW_CON, not a individual clk divider. I think
it's not suitable to use `struct clk_div_table` here.