diff mbox series

[RFC,2/2] clk: mediatek: Add frequency hopping support

Message ID 20220612135414.3003-3-johnson.wang@mediatek.com (mailing list archive)
State New, archived
Headers show
Series Introduce MediaTek frequency hopping driver | expand

Commit Message

Johnson Wang June 12, 2022, 1:54 p.m. UTC
Add frequency hopping support and spread spectrum clocking
control for MT8186.

Signed-off-by: Edward-JW Yang <edward-jw.yang@mediatek.com>
Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
---
 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

Comments

AngeloGioacchino Del Regno June 13, 2022, 9:43 a.m. UTC | #1
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@mediatek.com>
> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>

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`).

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.

> +	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?

Also, at least these three field settings are common between (rate > 0) and
(rate <= 0), so they can go outside of the conditional.

> +
> +		/* 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... :-)

> +
> +	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.

> +
> +	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?

> +}
> +
> +static void __set_postdiv(struct fh_pll_regs *regs, struct fh_pll_data *data,
> +			  int postdiv)
> +{
> +	unsigned int regval, temp;
> +
> +	for (regval = 0 ; regval < data->postdiv_table_size ; regval++) {
> +		if (data->postdiv_table[regval] > postdiv) {
> +			regval--;
> +			break;
> +		}
> +	}
> +
> +	temp = (readl(regs->reg_con_postdiv)) & ~(data->postdiv_mask);
> +	temp |= regval << data->postdiv_offset;
> +	writel(temp, regs->reg_con_postdiv);
> +}
> +
> +static int fhctl_ap_hopping(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;
> +	int ret = 0;
> +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> +	spinlock_t *lock = d->lock;
> +	unsigned long flags = 0;
> +	unsigned int pll_postdiv;
> +
> +	domain = d->domain;
> +	regs = &domain->regs[fh_id];
> +	data = &domain->data[fh_id];
> +
> +	if (postdiv > 0) {
> +		pll_postdiv = __get_postdiv(regs, data);
> +
> +		if (postdiv > pll_postdiv)
> +			__set_postdiv(regs, data, postdiv);
> +	}
> +
> +	spin_lock_irqsave(lock, flags);
> +
> +	ret = hopping_hw_flow(priv_data, domain_name, fh_id, new_dds, postdiv);
> +
> +	spin_unlock_irqrestore(lock, flags);
> +
> +	if (postdiv > 0) {
> +		if (postdiv < pll_postdiv)
> +			__set_postdiv(regs, data, postdiv);
> +	}
> +
> +	return ret;
> +}
> +
> +static int fhctl_ap_ssc_enable(void *priv_data, char *domain_name,
> +			       int fh_id, int rate)
> +{
> +	struct fh_pll_domain *domain;
> +	struct fh_pll_regs *regs;
> +	struct fh_pll_data *data;
> +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> +	spinlock_t *lock = d->lock;
> +	struct pll_dts *array = d->array;
> +	unsigned long flags = 0;
> +
> +	spin_lock_irqsave(lock, flags);
> +
> +	domain = d->domain;
> +	regs = &domain->regs[fh_id];
> +	data = &domain->data[fh_id];
> +
> +	fhctl_set_ssc_regs(regs, data, fh_id, rate);
> +
> +	array->ssc_rate = rate;
> +
> +	spin_unlock_irqrestore(lock, flags);
> +
> +	return 0;
> +}
> +
> +static int fhctl_ap_ssc_disable(void *priv_data, char *domain_name, int fh_id)
> +{
> +	struct fh_pll_domain *domain;
> +	struct fh_pll_regs *regs;
> +	struct fh_pll_data *data;
> +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> +	spinlock_t *lock = d->lock;
> +	struct pll_dts *array = d->array;
> +	unsigned long flags = 0;
> +
> +	spin_lock_irqsave(lock, flags);
> +
> +	domain = d->domain;
> +	regs = &domain->regs[fh_id];
> +	data = &domain->data[fh_id];
> +
> +	fhctl_set_ssc_regs(regs, data, fh_id, 0);
> +
> +	array->ssc_rate = 0;
> +
> +	spin_unlock_irqrestore(lock, flags);
> +
> +	return 0;
> +}


Just commonize these two...

static int __fhctl_ap_ssc_enable(struct hdlr_data *d, int fh_id, int rate)
{
	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;

	spinlock_t *lock = d->lock;

	unsigned long flags = 0;



	spin_lock_irqsave(lock, flags);



	fhctl_set_ssc_regs(regs, data, fh_id, rate);



	array->ssc_rate = rate;



	spin_unlock_irqrestore(lock, flags);



	return 0;
}

static int fhctl_ap_ssc_enable(void *priv_data, char *domain_name, int fh_id, int rate)
{
	return __fhctl_ap_ssc_enable((struct hdlr_data *)priv_data, domain_name,
				     fh_id, rate);
}

static int fhctl_ap_ssc_disable(void *priv_data, char *domain_name, int fh_id)
{
	return __fhctl_ap_ssc_enable((struct hdlr_data *)priv_data, domain_name,
				     fh_id, 0);
}


> +
> +static int fhctl_ap_hw_init(struct pll_dts *array, struct fh_ap_match *match)
> +{
> +	static DEFINE_SPINLOCK(lock);
> +	struct hdlr_data *priv_data;
> +	struct fh_hdlr *hdlr;
> +	struct fh_pll_domain *domain;
> +	int fh_id = array->fh_id;
> +	struct fh_pll_regs *regs;
> +	struct fh_pll_data *data;
> +	int mask = BIT(fh_id);
> +
> +	priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
> +	hdlr = kzalloc(sizeof(*hdlr), GFP_KERNEL);
> +	init_fh_domain(array->domain, array->comp, array->fhctl_base,
> +		       array->apmixed_base);
> +
> +	priv_data->array = array;
> +	priv_data->lock = &lock;
> +	priv_data->domain = get_fh_domain(array->domain);
> +
> +	/* do HW init */
> +	domain = priv_data->domain;
> +	regs = &domain->regs[fh_id];
> +	data = &domain->data[fh_id];
> +
> +	fh_set_field(regs->reg_clk_con, mask, 1);
> +	fh_set_field(regs->reg_rst_con, mask, 0);
> +	fh_set_field(regs->reg_rst_con, mask, 1);
> +	writel(0x0, regs->reg_cfg);
> +	writel(0x0, regs->reg_updnlmt);
> +	writel(0x0, regs->reg_dds);
> +
> +	/* hook to array */
> +	hdlr->data = priv_data;
> +	hdlr->ops = match->hdlr->ops;
> +	/* hook hdlr to array is the last step */
> +	mb();

I really don't think that you need this barrier here - if there's something that
I have misunderstood about that, please provide an extensive explanation.

> +	array->hdlr = hdlr;
> +
> +	/* do SSC */
> +	if (array->ssc_rate) {
> +		struct fh_hdlr *hdlr = array->hdlr;
> +
> +		hdlr->ops->ssc_enable(hdlr->data, array->domain, array->fh_id,
> +				      array->ssc_rate);
> +	}
> +
> +	return 0;
> +}
> +
> +static struct fh_operation fhctl_ap_ops = {
> +	.hopping = fhctl_ap_hopping,
> +	.ssc_enable = fhctl_ap_ssc_enable,
> +	.ssc_disable = fhctl_ap_ssc_disable,
> +};
> +
> +static struct fh_hdlr mt8186_hdlr = {
> +	.ops = &fhctl_ap_ops,
> +};
> +
> +static struct fh_ap_match mt8186_match = {
> +	.name = "mediatek,mt8186-fhctl",
> +	.hdlr = &mt8186_hdlr,
> +	.init = &fhctl_ap_hw_init,
> +};
> +
> +static struct fh_ap_match *matches[] = {
> +	&mt8186_match,
> +	NULL,
> +};
> +
> +int fhctl_ap_init(struct pll_dts *array)
> +{
> +	int i;
> +	int num_pll = array->num_pll;
> +	struct fh_ap_match **match = matches;
> +
> +	/* find match by compatible */
> +	for (i = 0; i < ARRAY_SIZE(matches); i++) {
> +		char *comp = (*match)->name;
> +		char *target = array->comp;
> +
> +		if (!strcmp(comp, target))
> +			break;
> +		match++;
> +	}
> +
> +	if (*match == NULL)
> +		return -1;
> +
> +	/* init flow for every pll */
> +	for (i = 0; i < num_pll; i++, array++) {
> +		char *method = array->method;
> +
> +		if (!strcmp(method, FHCTL_TARGET))
> +			(*match)->init(array, *match);
> +	}
> +
> +	return 0;
> +}
> diff --git a/drivers/clk/mediatek/clk-fhctl-pll.c b/drivers/clk/mediatek/clk-fhctl-pll.c
> new file mode 100644
> index 000000000000..b3ccbbd04e1b
> --- /dev/null
> +++ b/drivers/clk/mediatek/clk-fhctl-pll.c
> @@ -0,0 +1,209 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include "clk-fhctl-pll.h"
> +#include "clk-fhctl-util.h"
> +
> +#define REG_ADDR(base, x) ((void __iomem *)((unsigned long)base + (x)))
> +
> +struct fh_pll_match {
> +	char *compatible;
> +	struct fh_pll_domain **domain_list;
> +};
> +
> +static int fhctl_pll_init(struct fh_pll_domain *d, void __iomem *fhctl_base,
> +			  void __iomem *apmixed_base)
> +{
> +	struct fh_pll_data *data = d->data;
> +	struct fh_pll_offset *offset = d->offset;
> +	struct fh_pll_regs *regs = d->regs;
> +
> +	if (regs->reg_hp_en)
> +		return 0;
> +
> +	while (data->dds_mask != 0) {
> +		int regs_offset;
> +
> +		/* fhctl common part */
> +		regs->reg_hp_en = REG_ADDR(fhctl_base, offset->offset_hp_en);
> +		regs->reg_clk_con = REG_ADDR(fhctl_base,
> +					     offset->offset_clk_con);
> +		regs->reg_rst_con = REG_ADDR(fhctl_base,
> +					     offset->offset_rst_con);
> +		regs->reg_slope0 = REG_ADDR(fhctl_base, offset->offset_slope0);
> +		regs->reg_slope1 = REG_ADDR(fhctl_base, offset->offset_slope1);
> +
> +		/* fhctl pll part */
> +		regs_offset = offset->offset_fhctl + offset->offset_cfg;
> +		regs->reg_cfg = REG_ADDR(fhctl_base, regs_offset);
> +		regs->reg_updnlmt = REG_ADDR(regs->reg_cfg,
> +					     offset->offset_updnlmt);
> +		regs->reg_dds = REG_ADDR(regs->reg_cfg, offset->offset_dds);
> +		regs->reg_dvfs = REG_ADDR(regs->reg_cfg, offset->offset_dvfs);
> +		regs->reg_mon = REG_ADDR(regs->reg_cfg, offset->offset_mon);
> +
> +		/* apmixed part */
> +		regs->reg_con_pcw = REG_ADDR(apmixed_base,
> +					     offset->offset_con_pcw);
> +		regs->reg_con_postdiv = REG_ADDR(apmixed_base,
> +						 offset->offset_con_postdiv);
> +
> +		data++;
> +		offset++;
> +		regs++;
> +	}
> +
> +	return 0;
> +}
> +
> +static unsigned int __postdiv_pow_tbl[8] = {1, 2, 4, 8, 16, 1, 1, 1};
> +#define POSTDIV_TABLE_SIZE (sizeof(__postdiv_pow_tbl)\
> +	/sizeof(unsigned int))
> +
> +#define SIZE_8186_TOP (sizeof(mt8186_top_data)\
> +	/sizeof(struct fh_pll_data))
> +#define DATA_8186_TOP(_name) {						\

Is it just only about the name?
I mean, are there other SoCs for which each PLL needs different parameters?

If not, there are many ways to commonize these params instead of repeating
them over and over again...

> +		.name = _name,						\
> +		.dds_mask = GENMASK(21, 0),				\
> +		.postdiv_mask = GENMASK(26, 24),			\
> +		.postdiv_offset = 24,					\
> +		.postdiv_table = __postdiv_pow_tbl,			\
> +		.postdiv_table_size = POSTDIV_TABLE_SIZE,		\
> +		.slope0_value = 0x6003c97,				\
> +		.slope1_value = 0x6003c97,				\
> +		.sfstrx_en = BIT(2),					\
> +		.frddsx_en = BIT(1),					\
> +		.fhctlx_en = BIT(0),					\
> +		.tgl_org = BIT(31),					\
> +		.dvfs_tri = BIT(31),					\
> +		.pcwchg = BIT(31),					\
> +		.dt_val = 0x0,						\
> +		.df_val = 0x9,						\
> +		.updnlmt_shft = 16,					\
> +		.msk_frddsx_dys = GENMASK(23, 20),			\
> +		.msk_frddsx_dts = GENMASK(19, 16),			\
> +	}
> +#define OFFSET_8186_TOP(_fhctl, _con_pcw) {				\
> +		.offset_fhctl = _fhctl,					\
> +		.offset_con_pcw = _con_pcw,				\
> +		.offset_con_postdiv = _con_pcw,				\
> +		.offset_hp_en = 0x0,					\
> +		.offset_clk_con = 0x8,					\
> +		.offset_rst_con = 0xc,					\
> +		.offset_slope0 = 0x10,					\
> +		.offset_slope1 = 0x14,					\
> +		.offset_cfg = 0x0,					\
> +		.offset_updnlmt = 0x4,					\
> +		.offset_dds = 0x8,					\
> +		.offset_dvfs = 0xc,					\
> +		.offset_mon = 0x10,					\
> +	}
> +static struct fh_pll_data mt8186_top_data[] = {
> +	DATA_8186_TOP("armpll_ll"),
> +	DATA_8186_TOP("armpll_bl"),
> +	DATA_8186_TOP("ccipll"),
> +	DATA_8186_TOP("mainpll"),
> +	DATA_8186_TOP("mmpll"),
> +	DATA_8186_TOP("tvdpll"),
> +	DATA_8186_TOP("mpll"),
> +	DATA_8186_TOP("adsppll"),
> +	DATA_8186_TOP("mfgpll"),
> +	DATA_8186_TOP("nnapll"),
> +	DATA_8186_TOP("nna2pll"),
> +	DATA_8186_TOP("msdcpll"),
> +	DATA_8186_TOP("mempll"),
> +	{}
> +};
> +static struct fh_pll_offset mt8186_top_offset[] = {
> +	OFFSET_8186_TOP(0x003C, 0x0208),
> +	OFFSET_8186_TOP(0x0050, 0x0218),
> +	OFFSET_8186_TOP(0x0064, 0x0228),
> +	OFFSET_8186_TOP(0x0078, 0x0248),
> +	OFFSET_8186_TOP(0x008C, 0x0258),
> +	OFFSET_8186_TOP(0x00A0, 0x0268),
> +	OFFSET_8186_TOP(0x00B4, 0x0278),
> +	OFFSET_8186_TOP(0x00C8, 0x0308),
> +	OFFSET_8186_TOP(0x00DC, 0x0318),
> +	OFFSET_8186_TOP(0x00F0, 0x0360),
> +	OFFSET_8186_TOP(0x0104, 0x0370),
> +	OFFSET_8186_TOP(0x0118, 0x0390),
> +	OFFSET_8186_TOP(0x012c, 0xdeb1),
> +	{}
> +};
> +static struct fh_pll_regs mt8186_top_regs[SIZE_8186_TOP];
> +static struct fh_pll_domain mt8186_top = {
> +	.name = "top",
> +	.data = (struct fh_pll_data *)&mt8186_top_data,
> +	.offset = (struct fh_pll_offset *)&mt8186_top_offset,
> +	.regs = (struct fh_pll_regs *)&mt8186_top_regs,
> +	.init = &fhctl_pll_init,
> +};
> +static struct fh_pll_domain *mt8186_domain[] = {
> +	&mt8186_top,
> +	NULL,
> +};
> +static struct fh_pll_match mt8186_match = {
> +	.compatible = "mediatek,mt8186-fhctl",
> +	.domain_list = (struct fh_pll_domain **)mt8186_domain,
> +};
> +
> +static const struct fh_pll_match *matches[] = {
> +	&mt8186_match,
> +	NULL
> +};
> +
> +
> +static struct fh_pll_domain **get_list(char *comp)
> +{
> +	struct fh_pll_match **match;
> +	static struct fh_pll_domain **list;
> +	int i;
> +
> +	match = (struct fh_pll_match **)matches;
> +
> +	/* name used only if !list */
> +	if (!list) {
> +		for (i = 0; i < ARRAY_SIZE(matches); i++) {
> +			if (!strcmp(comp, (*match)->compatible)) {
> +				list = (*match)->domain_list;
> +				break;
> +			}
> +			match++;
> +		}
> +	}
> +	return list;
> +}
> +void init_fh_domain(const char *domain, char *comp, void __iomem *fhctl_base,
> +		    void __iomem *apmixed_base)
> +{
> +	struct fh_pll_domain **list;
> +
> +	list = get_list(comp);
> +
> +	while (*list != NULL) {
> +		if (!strcmp(domain, (*list)->name)) {
> +			(*list)->init(*list, fhctl_base, apmixed_base);
> +			return;
> +		}
> +		list++;
> +	}
> +}
> +
> +struct fh_pll_domain *get_fh_domain(const char *domain)
> +{
> +	struct fh_pll_domain **list;
> +
> +	list = get_list(NULL);
> +
> +	/* find instance */
> +	while (*list != NULL) {
> +		if (!strcmp(domain, (*list)->name))
> +			return *list;
> +		list++;
> +	}
> +	return NULL;
> +}
> diff --git a/drivers/clk/mediatek/clk-fhctl-pll.h b/drivers/clk/mediatek/clk-fhctl-pll.h
> new file mode 100644
> index 000000000000..7f0f7577f7a5
> --- /dev/null
> +++ b/drivers/clk/mediatek/clk-fhctl-pll.h
> @@ -0,0 +1,74 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2022 MediaTek Inc.
> + */
> +
> +#ifndef __CLK_FHCTL_PLL_H
> +#define __CLK_FHCTL_PLL_H
> +#include <linux/types.h>
> +#include <linux/bitops.h>
> +#include <linux/iopoll.h>
> +
> +struct fh_pll_data {
> +	char *name;
> +	unsigned int dds_mask;
> +	unsigned int postdiv_mask;
> +	unsigned int postdiv_offset;
> +	unsigned int *postdiv_table;
> +	unsigned int postdiv_table_size;
> +	unsigned int slope0_value;
> +	unsigned int slope1_value;
> +	unsigned int sfstrx_en;
> +	unsigned int frddsx_en;
> +	unsigned int fhctlx_en;
> +	unsigned int tgl_org;
> +	unsigned int dvfs_tri;
> +	unsigned int pcwchg;
> +	unsigned int dt_val;
> +	unsigned int df_val;
> +	unsigned int updnlmt_shft;
> +	unsigned int msk_frddsx_dys;
> +	unsigned int msk_frddsx_dts;

That's describing some hardware, so... you should really use `u32` here
as that would be more descriptive.

> +};
> +struct fh_pll_offset {
> +	int offset_fhctl;
> +	int offset_con_pcw;
> +	int offset_con_postdiv;
> +	int offset_hp_en;
> +	int offset_clk_con;
> +	int offset_rst_con;
> +	int offset_slope0;
> +	int offset_slope1;
> +	int offset_cfg;
> +	int offset_updnlmt;
> +	int offset_dds;
> +	int offset_dvfs;
> +	int offset_mon;

These also seem to be describing HW registers, and I don't think that
we'll ever see a negative offset for any of these?

> +};
> +struct fh_pll_regs {
> +	void __iomem *reg_hp_en;
> +	void __iomem *reg_clk_con;
> +	void __iomem *reg_rst_con;
> +	void __iomem *reg_slope0;
> +	void __iomem *reg_slope1;
> +	void __iomem *reg_cfg;
> +	void __iomem *reg_updnlmt;
> +	void __iomem *reg_dds;
> +	void __iomem *reg_dvfs;
> +	void __iomem *reg_mon;
> +	void __iomem *reg_con_pcw;
> +	void __iomem *reg_con_postdiv;
> +};
> +struct fh_pll_domain {
> +	char *name;
> +	struct fh_pll_data *data;
> +	struct fh_pll_offset *offset;
> +	struct fh_pll_regs *regs;
> +	int (*init)(struct fh_pll_domain *d, void __iomem *fhctl_base,
> +		    void __iomem *apmixed_base);
> +};
> +extern struct fh_pll_domain *get_fh_domain(const char *name);
> +extern void init_fh_domain(const char *domain_name, char *comp_name,
> +			   void __iomem *fhctl_base,
> +			   void __iomem *apmixed_base);
> +#endif

...and I think this covers enough of the code that may be transferred to clk-pll.

Excited to see the next iteration of this one!

Cheers,
Angelo
Edward-JW Yang June 24, 2022, 7:12 a.m. UTC | #2
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@mediatek.com>
> > Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> 
> 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.

> 
> > +}
> > +
> > +static void __set_postdiv(struct fh_pll_regs *regs, struct fh_pll_data *data,
> > +			  int postdiv)
> > +{
> > +	unsigned int regval, temp;
> > +
> > +	for (regval = 0 ; regval < data->postdiv_table_size ; regval++) {
> > +		if (data->postdiv_table[regval] > postdiv) {
> > +			regval--;
> > +			break;
> > +		}
> > +	}
> > +
> > +	temp = (readl(regs->reg_con_postdiv)) & ~(data->postdiv_mask);
> > +	temp |= regval << data->postdiv_offset;
> > +	writel(temp, regs->reg_con_postdiv);
> > +}
> > +
> > +static int fhctl_ap_hopping(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;
> > +	int ret = 0;
> > +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> > +	spinlock_t *lock = d->lock;
> > +	unsigned long flags = 0;
> > +	unsigned int pll_postdiv;
> > +
> > +	domain = d->domain;
> > +	regs = &domain->regs[fh_id];
> > +	data = &domain->data[fh_id];
> > +
> > +	if (postdiv > 0) {
> > +		pll_postdiv = __get_postdiv(regs, data);
> > +
> > +		if (postdiv > pll_postdiv)
> > +			__set_postdiv(regs, data, postdiv);
> > +	}
> > +
> > +	spin_lock_irqsave(lock, flags);
> > +
> > +	ret = hopping_hw_flow(priv_data, domain_name, fh_id, new_dds, postdiv);
> > +
> > +	spin_unlock_irqrestore(lock, flags);
> > +
> > +	if (postdiv > 0) {
> > +		if (postdiv < pll_postdiv)
> > +			__set_postdiv(regs, data, postdiv);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int fhctl_ap_ssc_enable(void *priv_data, char *domain_name,
> > +			       int fh_id, int rate)
> > +{
> > +	struct fh_pll_domain *domain;
> > +	struct fh_pll_regs *regs;
> > +	struct fh_pll_data *data;
> > +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> > +	spinlock_t *lock = d->lock;
> > +	struct pll_dts *array = d->array;
> > +	unsigned long flags = 0;
> > +
> > +	spin_lock_irqsave(lock, flags);
> > +
> > +	domain = d->domain;
> > +	regs = &domain->regs[fh_id];
> > +	data = &domain->data[fh_id];
> > +
> > +	fhctl_set_ssc_regs(regs, data, fh_id, rate);
> > +
> > +	array->ssc_rate = rate;
> > +
> > +	spin_unlock_irqrestore(lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +static int fhctl_ap_ssc_disable(void *priv_data, char *domain_name, int fh_id)
> > +{
> > +	struct fh_pll_domain *domain;
> > +	struct fh_pll_regs *regs;
> > +	struct fh_pll_data *data;
> > +	struct hdlr_data *d = (struct hdlr_data *)priv_data;
> > +	spinlock_t *lock = d->lock;
> > +	struct pll_dts *array = d->array;
> > +	unsigned long flags = 0;
> > +
> > +	spin_lock_irqsave(lock, flags);
> > +
> > +	domain = d->domain;
> > +	regs = &domain->regs[fh_id];
> > +	data = &domain->data[fh_id];
> > +
> > +	fhctl_set_ssc_regs(regs, data, fh_id, 0);
> > +
> > +	array->ssc_rate = 0;
> > +
> > +	spin_unlock_irqrestore(lock, flags);
> > +
> > +	return 0;
> > +}
> 
> 
> Just commonize these two...
> 
> static int __fhctl_ap_ssc_enable(struct hdlr_data *d, int fh_id, int rate)
> {
> 	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;
> 
> 	spinlock_t *lock = d->lock;
> 
> 	unsigned long flags = 0;
> 
> 
> 
> 	spin_lock_irqsave(lock, flags);
> 
> 
> 
> 	fhctl_set_ssc_regs(regs, data, fh_id, rate);
> 
> 
> 
> 	array->ssc_rate = rate;
> 
> 
> 
> 	spin_unlock_irqrestore(lock, flags);
> 
> 
> 
> 	return 0;
> }
> 
> static int fhctl_ap_ssc_enable(void *priv_data, char *domain_name, int fh_id, int rate)
> {
> 	return __fhctl_ap_ssc_enable((struct hdlr_data *)priv_data, domain_name,
> 				     fh_id, rate);
> }
> 
> static int fhctl_ap_ssc_disable(void *priv_data, char *domain_name, int fh_id)
> {
> 	return __fhctl_ap_ssc_enable((struct hdlr_data *)priv_data, domain_name,
> 				     fh_id, 0);
> }

OK, we will commonize these. Thanks.

> 
> 
> > +
> > +static int fhctl_ap_hw_init(struct pll_dts *array, struct fh_ap_match *match)
> > +{
> > +	static DEFINE_SPINLOCK(lock);
> > +	struct hdlr_data *priv_data;
> > +	struct fh_hdlr *hdlr;
> > +	struct fh_pll_domain *domain;
> > +	int fh_id = array->fh_id;
> > +	struct fh_pll_regs *regs;
> > +	struct fh_pll_data *data;
> > +	int mask = BIT(fh_id);
> > +
> > +	priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
> > +	hdlr = kzalloc(sizeof(*hdlr), GFP_KERNEL);
> > +	init_fh_domain(array->domain, array->comp, array->fhctl_base,
> > +		       array->apmixed_base);
> > +
> > +	priv_data->array = array;
> > +	priv_data->lock = &lock;
> > +	priv_data->domain = get_fh_domain(array->domain);
> > +
> > +	/* do HW init */
> > +	domain = priv_data->domain;
> > +	regs = &domain->regs[fh_id];
> > +	data = &domain->data[fh_id];
> > +
> > +	fh_set_field(regs->reg_clk_con, mask, 1);
> > +	fh_set_field(regs->reg_rst_con, mask, 0);
> > +	fh_set_field(regs->reg_rst_con, mask, 1);
> > +	writel(0x0, regs->reg_cfg);
> > +	writel(0x0, regs->reg_updnlmt);
> > +	writel(0x0, regs->reg_dds);
> > +
> > +	/* hook to array */
> > +	hdlr->data = priv_data;
> > +	hdlr->ops = match->hdlr->ops;
> > +	/* hook hdlr to array is the last step */
> > +	mb();
> 
> I really don't think that you need this barrier here - if there's something that
> I have misunderstood about that, please provide an extensive explanation.

Actually we can remove this mb(), the original thought is that make sure once
hdlr is used, hdlr->data and hdlr->ops is prepared.
After review, hdlr won't be used during fhctl_ap_hw_init, so it can be safely
removed. Thanks.

> 
> > +	array->hdlr = hdlr;
> > +
> > +	/* do SSC */
> > +	if (array->ssc_rate) {
> > +		struct fh_hdlr *hdlr = array->hdlr;
> > +
> > +		hdlr->ops->ssc_enable(hdlr->data, array->domain, array->fh_id,
> > +				      array->ssc_rate);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static struct fh_operation fhctl_ap_ops = {
> > +	.hopping = fhctl_ap_hopping,
> > +	.ssc_enable = fhctl_ap_ssc_enable,
> > +	.ssc_disable = fhctl_ap_ssc_disable,
> > +};
> > +
> > +static struct fh_hdlr mt8186_hdlr = {
> > +	.ops = &fhctl_ap_ops,
> > +};
> > +
> > +static struct fh_ap_match mt8186_match = {
> > +	.name = "mediatek,mt8186-fhctl",
> > +	.hdlr = &mt8186_hdlr,
> > +	.init = &fhctl_ap_hw_init,
> > +};
> > +
> > +static struct fh_ap_match *matches[] = {
> > +	&mt8186_match,
> > +	NULL,
> > +};
> > +
> > +int fhctl_ap_init(struct pll_dts *array)
> > +{
> > +	int i;
> > +	int num_pll = array->num_pll;
> > +	struct fh_ap_match **match = matches;
> > +
> > +	/* find match by compatible */
> > +	for (i = 0; i < ARRAY_SIZE(matches); i++) {
> > +		char *comp = (*match)->name;
> > +		char *target = array->comp;
> > +
> > +		if (!strcmp(comp, target))
> > +			break;
> > +		match++;
> > +	}
> > +
> > +	if (*match == NULL)
> > +		return -1;
> > +
> > +	/* init flow for every pll */
> > +	for (i = 0; i < num_pll; i++, array++) {
> > +		char *method = array->method;
> > +
> > +		if (!strcmp(method, FHCTL_TARGET))
> > +			(*match)->init(array, *match);
> > +	}
> > +
> > +	return 0;
> > +}
> > diff --git a/drivers/clk/mediatek/clk-fhctl-pll.c b/drivers/clk/mediatek/clk-fhctl-pll.c
> > new file mode 100644
> > index 000000000000..b3ccbbd04e1b
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-fhctl-pll.c
> > @@ -0,0 +1,209 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2022 MediaTek Inc.
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/module.h>
> > +#include "clk-fhctl-pll.h"
> > +#include "clk-fhctl-util.h"
> > +
> > +#define REG_ADDR(base, x) ((void __iomem *)((unsigned long)base + (x)))
> > +
> > +struct fh_pll_match {
> > +	char *compatible;
> > +	struct fh_pll_domain **domain_list;
> > +};
> > +
> > +static int fhctl_pll_init(struct fh_pll_domain *d, void __iomem *fhctl_base,
> > +			  void __iomem *apmixed_base)
> > +{
> > +	struct fh_pll_data *data = d->data;
> > +	struct fh_pll_offset *offset = d->offset;
> > +	struct fh_pll_regs *regs = d->regs;
> > +
> > +	if (regs->reg_hp_en)
> > +		return 0;
> > +
> > +	while (data->dds_mask != 0) {
> > +		int regs_offset;
> > +
> > +		/* fhctl common part */
> > +		regs->reg_hp_en = REG_ADDR(fhctl_base, offset->offset_hp_en);
> > +		regs->reg_clk_con = REG_ADDR(fhctl_base,
> > +					     offset->offset_clk_con);
> > +		regs->reg_rst_con = REG_ADDR(fhctl_base,
> > +					     offset->offset_rst_con);
> > +		regs->reg_slope0 = REG_ADDR(fhctl_base, offset->offset_slope0);
> > +		regs->reg_slope1 = REG_ADDR(fhctl_base, offset->offset_slope1);
> > +
> > +		/* fhctl pll part */
> > +		regs_offset = offset->offset_fhctl + offset->offset_cfg;
> > +		regs->reg_cfg = REG_ADDR(fhctl_base, regs_offset);
> > +		regs->reg_updnlmt = REG_ADDR(regs->reg_cfg,
> > +					     offset->offset_updnlmt);
> > +		regs->reg_dds = REG_ADDR(regs->reg_cfg, offset->offset_dds);
> > +		regs->reg_dvfs = REG_ADDR(regs->reg_cfg, offset->offset_dvfs);
> > +		regs->reg_mon = REG_ADDR(regs->reg_cfg, offset->offset_mon);
> > +
> > +		/* apmixed part */
> > +		regs->reg_con_pcw = REG_ADDR(apmixed_base,
> > +					     offset->offset_con_pcw);
> > +		regs->reg_con_postdiv = REG_ADDR(apmixed_base,
> > +						 offset->offset_con_postdiv);
> > +
> > +		data++;
> > +		offset++;
> > +		regs++;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static unsigned int __postdiv_pow_tbl[8] = {1, 2, 4, 8, 16, 1, 1, 1};
> > +#define POSTDIV_TABLE_SIZE (sizeof(__postdiv_pow_tbl)\
> > +	/sizeof(unsigned int))
> > +
> > +#define SIZE_8186_TOP (sizeof(mt8186_top_data)\
> > +	/sizeof(struct fh_pll_data))
> > +#define DATA_8186_TOP(_name) {						\
> 
> Is it just only about the name?
> I mean, are there other SoCs for which each PLL needs different parameters?
> 
> If not, there are many ways to commonize these params instead of repeating
> them over and over again...

Some parameters are difference on other SoCs. We will find a way to commonize
these. Thanks.

> 
> > +		.name = _name,						\
> > +		.dds_mask = GENMASK(21, 0),				\
> > +		.postdiv_mask = GENMASK(26, 24),			\
> > +		.postdiv_offset = 24,					\
> > +		.postdiv_table = __postdiv_pow_tbl,			\
> > +		.postdiv_table_size = POSTDIV_TABLE_SIZE,		\
> > +		.slope0_value = 0x6003c97,				\
> > +		.slope1_value = 0x6003c97,				\
> > +		.sfstrx_en = BIT(2),					\
> > +		.frddsx_en = BIT(1),					\
> > +		.fhctlx_en = BIT(0),					\
> > +		.tgl_org = BIT(31),					\
> > +		.dvfs_tri = BIT(31),					\
> > +		.pcwchg = BIT(31),					\
> > +		.dt_val = 0x0,						\
> > +		.df_val = 0x9,						\
> > +		.updnlmt_shft = 16,					\
> > +		.msk_frddsx_dys = GENMASK(23, 20),			\
> > +		.msk_frddsx_dts = GENMASK(19, 16),			\
> > +	}
> > +#define OFFSET_8186_TOP(_fhctl, _con_pcw) {				\
> > +		.offset_fhctl = _fhctl,					\
> > +		.offset_con_pcw = _con_pcw,				\
> > +		.offset_con_postdiv = _con_pcw,				\
> > +		.offset_hp_en = 0x0,					\
> > +		.offset_clk_con = 0x8,					\
> > +		.offset_rst_con = 0xc,					\
> > +		.offset_slope0 = 0x10,					\
> > +		.offset_slope1 = 0x14,					\
> > +		.offset_cfg = 0x0,					\
> > +		.offset_updnlmt = 0x4,					\
> > +		.offset_dds = 0x8,					\
> > +		.offset_dvfs = 0xc,					\
> > +		.offset_mon = 0x10,					\
> > +	}
> > +static struct fh_pll_data mt8186_top_data[] = {
> > +	DATA_8186_TOP("armpll_ll"),
> > +	DATA_8186_TOP("armpll_bl"),
> > +	DATA_8186_TOP("ccipll"),
> > +	DATA_8186_TOP("mainpll"),
> > +	DATA_8186_TOP("mmpll"),
> > +	DATA_8186_TOP("tvdpll"),
> > +	DATA_8186_TOP("mpll"),
> > +	DATA_8186_TOP("adsppll"),
> > +	DATA_8186_TOP("mfgpll"),
> > +	DATA_8186_TOP("nnapll"),
> > +	DATA_8186_TOP("nna2pll"),
> > +	DATA_8186_TOP("msdcpll"),
> > +	DATA_8186_TOP("mempll"),
> > +	{}
> > +};
> > +static struct fh_pll_offset mt8186_top_offset[] = {
> > +	OFFSET_8186_TOP(0x003C, 0x0208),
> > +	OFFSET_8186_TOP(0x0050, 0x0218),
> > +	OFFSET_8186_TOP(0x0064, 0x0228),
> > +	OFFSET_8186_TOP(0x0078, 0x0248),
> > +	OFFSET_8186_TOP(0x008C, 0x0258),
> > +	OFFSET_8186_TOP(0x00A0, 0x0268),
> > +	OFFSET_8186_TOP(0x00B4, 0x0278),
> > +	OFFSET_8186_TOP(0x00C8, 0x0308),
> > +	OFFSET_8186_TOP(0x00DC, 0x0318),
> > +	OFFSET_8186_TOP(0x00F0, 0x0360),
> > +	OFFSET_8186_TOP(0x0104, 0x0370),
> > +	OFFSET_8186_TOP(0x0118, 0x0390),
> > +	OFFSET_8186_TOP(0x012c, 0xdeb1),
> > +	{}
> > +};
> > +static struct fh_pll_regs mt8186_top_regs[SIZE_8186_TOP];
> > +static struct fh_pll_domain mt8186_top = {
> > +	.name = "top",
> > +	.data = (struct fh_pll_data *)&mt8186_top_data,
> > +	.offset = (struct fh_pll_offset *)&mt8186_top_offset,
> > +	.regs = (struct fh_pll_regs *)&mt8186_top_regs,
> > +	.init = &fhctl_pll_init,
> > +};
> > +static struct fh_pll_domain *mt8186_domain[] = {
> > +	&mt8186_top,
> > +	NULL,
> > +};
> > +static struct fh_pll_match mt8186_match = {
> > +	.compatible = "mediatek,mt8186-fhctl",
> > +	.domain_list = (struct fh_pll_domain **)mt8186_domain,
> > +};
> > +
> > +static const struct fh_pll_match *matches[] = {
> > +	&mt8186_match,
> > +	NULL
> > +};
> > +
> > +
> > +static struct fh_pll_domain **get_list(char *comp)
> > +{
> > +	struct fh_pll_match **match;
> > +	static struct fh_pll_domain **list;
> > +	int i;
> > +
> > +	match = (struct fh_pll_match **)matches;
> > +
> > +	/* name used only if !list */
> > +	if (!list) {
> > +		for (i = 0; i < ARRAY_SIZE(matches); i++) {
> > +			if (!strcmp(comp, (*match)->compatible)) {
> > +				list = (*match)->domain_list;
> > +				break;
> > +			}
> > +			match++;
> > +		}
> > +	}
> > +	return list;
> > +}
> > +void init_fh_domain(const char *domain, char *comp, void __iomem *fhctl_base,
> > +		    void __iomem *apmixed_base)
> > +{
> > +	struct fh_pll_domain **list;
> > +
> > +	list = get_list(comp);
> > +
> > +	while (*list != NULL) {
> > +		if (!strcmp(domain, (*list)->name)) {
> > +			(*list)->init(*list, fhctl_base, apmixed_base);
> > +			return;
> > +		}
> > +		list++;
> > +	}
> > +}
> > +
> > +struct fh_pll_domain *get_fh_domain(const char *domain)
> > +{
> > +	struct fh_pll_domain **list;
> > +
> > +	list = get_list(NULL);
> > +
> > +	/* find instance */
> > +	while (*list != NULL) {
> > +		if (!strcmp(domain, (*list)->name))
> > +			return *list;
> > +		list++;
> > +	}
> > +	return NULL;
> > +}
> > diff --git a/drivers/clk/mediatek/clk-fhctl-pll.h b/drivers/clk/mediatek/clk-fhctl-pll.h
> > new file mode 100644
> > index 000000000000..7f0f7577f7a5
> > --- /dev/null
> > +++ b/drivers/clk/mediatek/clk-fhctl-pll.h
> > @@ -0,0 +1,74 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2022 MediaTek Inc.
> > + */
> > +
> > +#ifndef __CLK_FHCTL_PLL_H
> > +#define __CLK_FHCTL_PLL_H
> > +#include <linux/types.h>
> > +#include <linux/bitops.h>
> > +#include <linux/iopoll.h>
> > +
> > +struct fh_pll_data {
> > +	char *name;
> > +	unsigned int dds_mask;
> > +	unsigned int postdiv_mask;
> > +	unsigned int postdiv_offset;
> > +	unsigned int *postdiv_table;
> > +	unsigned int postdiv_table_size;
> > +	unsigned int slope0_value;
> > +	unsigned int slope1_value;
> > +	unsigned int sfstrx_en;
> > +	unsigned int frddsx_en;
> > +	unsigned int fhctlx_en;
> > +	unsigned int tgl_org;
> > +	unsigned int dvfs_tri;
> > +	unsigned int pcwchg;
> > +	unsigned int dt_val;
> > +	unsigned int df_val;
> > +	unsigned int updnlmt_shft;
> > +	unsigned int msk_frddsx_dys;
> > +	unsigned int msk_frddsx_dts;
> 
> That's describing some hardware, so... you should really use `u32` here
> as that would be more descriptive.

OK, we will fix it. Thanks.

> 
> > +};
> > +struct fh_pll_offset {
> > +	int offset_fhctl;
> > +	int offset_con_pcw;
> > +	int offset_con_postdiv;
> > +	int offset_hp_en;
> > +	int offset_clk_con;
> > +	int offset_rst_con;
> > +	int offset_slope0;
> > +	int offset_slope1;
> > +	int offset_cfg;
> > +	int offset_updnlmt;
> > +	int offset_dds;
> > +	int offset_dvfs;
> > +	int offset_mon;
> 
> These also seem to be describing HW registers, and I don't think that
> we'll ever see a negative offset for any of these?

Oh, you are right, these are positive value. We will fix it. Thanks.

> 
> > +};
> > +struct fh_pll_regs {
> > +	void __iomem *reg_hp_en;
> > +	void __iomem *reg_clk_con;
> > +	void __iomem *reg_rst_con;
> > +	void __iomem *reg_slope0;
> > +	void __iomem *reg_slope1;
> > +	void __iomem *reg_cfg;
> > +	void __iomem *reg_updnlmt;
> > +	void __iomem *reg_dds;
> > +	void __iomem *reg_dvfs;
> > +	void __iomem *reg_mon;
> > +	void __iomem *reg_con_pcw;
> > +	void __iomem *reg_con_postdiv;
> > +};
> > +struct fh_pll_domain {
> > +	char *name;
> > +	struct fh_pll_data *data;
> > +	struct fh_pll_offset *offset;
> > +	struct fh_pll_regs *regs;
> > +	int (*init)(struct fh_pll_domain *d, void __iomem *fhctl_base,
> > +		    void __iomem *apmixed_base);
> > +};
> > +extern struct fh_pll_domain *get_fh_domain(const char *name);
> > +extern void init_fh_domain(const char *domain_name, char *comp_name,
> > +			   void __iomem *fhctl_base,
> > +			   void __iomem *apmixed_base);
> > +#endif
> 
> ...and I think this covers enough of the code that may be transferred to clk-pll.
> 
> Excited to see the next iteration of this one!
> 
> Cheers,
> Angelo
AngeloGioacchino Del Regno June 28, 2022, 10:09 a.m. UTC | #3
Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> 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@mediatek.com>
>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
>>
>> 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.

I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
In any case, this not being a *mandatory* component doesn't mean that when it is
enabled it's not changing the way we manage the PLLs..........

> 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

I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
the set_rate() logic of clk-pll.

> current set_rate method in clk-pll changes PLL register setting directly. Another
> way uses FHCTL to change PLL rate. 

...and of course, if we change that, we're effectively mutating the functionality
of the MediaTek clk-pll driver and please understand that seeing a clear mutation
in that driver is a bit more human-readable.

Besides, this makes me think about one question: is there any instance in which,
when FHCTL rate setting fails, we fall back to direct register writes?

I don't think that this is feasible because we have a register in FHCTL that
effectively hands over control to it, so direct register writes should not work
when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
sure that my understanding is right.

> We will set some PLL's frequency be controlled
> by clk-pll and some are controlled by FHCTL.

Another question: is this also changing on a per-board basis?

(note: the pll names in the example are random and not specific to anything)

Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
                  NNAPLL, MFGPLL
          board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL

Granted that the two A, B boards are using the same SoC, can that ever happen?

> And use `perms` param to decide
> whether a PLL is using FHCTL to change its frequency.

The perms param seems to be about:
  * Enabling debug (but you're not providing any way to actually use debugging
    features, so what's the point?)
  * Handing over PLL control to FHCTL for hopping (can be as well done with
    simply using a different .set_rate() callback instead of a flag)
  * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
    legit use for flags, but if it's just one flag, you can as well use a
    bool and manage this with a devicetree param like "enable-ssc")

That said, I think that the current way of enabling the FHCTL is more complicated
than how it should really be.

> 
> 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.
> 

Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
so much... it's really just a few register writes and nothing else, so I really
don't see where the problem is, here.

Another issue is that this driver may be largely incomplete, so perhaps I can't
really see the complications you're talking about? Is this the case?

Regarding keeping the FHCTL code in separated files, that's fine, but I would still
integrate it tightly in clk-pll and its registration flow, because - yes, this is
for sure not mandatory, but the main parameters are constant, they never change for
a specific PLL, as they're register offsets, bits and masks (which, again, will
never change as long as we're using the same SoC).

>>
>> 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?
> 

If it's important that the hardware has frddsx_en, sfstrx_en, fhctlx_en *before*
programming df_val/dt_val, then yes.... otherwise, just use writel_relaxed()
everywhere until the last write has to happen.

writel_relaxed(something,	a)
writel_relaxed(something_else,	b)
writel_relaxed(something_more,	a)
writel_relaxed(another_one,	c)
writel_relaxed(blah,		b)
writel(reg_cfg, 		X)

....but having a second look at it, this doesn't make a lot of sense because you
are anyway performing multiple writes to the same `reg_cfg`, so I think that you
can as well aggregate the writes in one and reduce the barriers like that, so
we'd have something like:


u32 pcw_val, val;

/* Important: This assumes that the contents of reg_cfg never change during the
    execution of this programming sequence. */
val = readl_relaxed(regs->reg_cfg);
pcw_val = readl_relaxed(regs->reg_con_pcw) & data->dds_mask;

/* Pause/disable Frequency Hopping controller for reconfiguration */


/* P.S.: can't we use FHCTLx_PAUSE instead of turning off?? */



/* SSC: Disable free-run mode */
val &= ~data->frddsx_en;

/* Disable Soft-start mode */
val &= ~data->sfstrx_en;

/* Disable Frequency Hopping controller */
val &= ~data->fhctlx_en;

writel(val, regs->reg_cfg);

/* **** warning: I'm covering only the enablement flow, not the disablement **** */

/* SSC Slope: Set delta frequency, delta time (df/dt) */
val |= data->df_val & data->msk_frddsx_dys;
val |= data->dt_val & data->msk_frddsx_dts;

/* is it important to write these before DDS?
  * no -> writel_relaxed; yes -> writel
  */
writel_relaxed(val, regs->reg_cfg);

/* Update PLL Toggle value */
writel_relaxed(pcw_val | data->tgl_org, regs->reg_dds);

/* SSC Swing: Calculate upper/lower limits */
updnlmt_val = PERCENT_TO_DDSLMT((readl_relaxed(regs->reg_dds) & data->dds_mask),
				rate << data->updnlmt_shft);
writel_relaxed(updnlmt_val, regs->reg_updnlmt);

/* Hand over PLL control to FHCTL */
fh_set_field(regs->reg_hp_en, BIT(fh_id), 1);

/* Re-Enable SSC and Hopping control */
val |= data->frddsx_en | data->fhctlx_en;
writel(val, data->reg_cfg);


Roughly, that's the idea.
Also, keep in mind that aggregating the writes when possible is already
improving the flow... relaxing R/W is another improvement though... but
beware that technically this is important only in performance paths (so
if this function gets called only very few times in a kernel life, it's
not really important to use _relaxed accessors).

Besides... I don't *really* like seeing the fh_{set, get}_field helpers...
they're confusing at best, and open-coding the R/W makes you able to
aggregate fields in one write without impacting on human readability.


>>
>> 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.
> 

Uhm, I don't think that `struct clk_div_table` is tied to individual clk dividers
in its definition... I mean, it shouldn't be a problem to reuse it in this case...
The advantage of it is that we are able to set a clear idx<->divider relation.

Anyway, if you have strong feelings about not using clk_div_table, it's ok,
unless anyone else has considerations about that.
Chen-Yu Tsai June 29, 2022, 8:54 a.m. UTC | #4
On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
<angelogioacchino.delregno@collabora.com> wrote:
>
> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> > 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@mediatek.com>
> >>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> >>
> >> 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.
>
> I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
> In any case, this not being a *mandatory* component doesn't mean that when it is
> enabled it's not changing the way we manage the PLLs..........
>
> > 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
>
> I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
> APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
> the set_rate() logic of clk-pll.
>
> > current set_rate method in clk-pll changes PLL register setting directly. Another
> > way uses FHCTL to change PLL rate.
>
> ...and of course, if we change that, we're effectively mutating the functionality
> of the MediaTek clk-pll driver and please understand that seeing a clear mutation
> in that driver is a bit more human-readable.
>
> Besides, this makes me think about one question: is there any instance in which,
> when FHCTL rate setting fails, we fall back to direct register writes?
>
> I don't think that this is feasible because we have a register in FHCTL that
> effectively hands over control to it, so direct register writes should not work
> when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
> sure that my understanding is right.
>
> > We will set some PLL's frequency be controlled
> > by clk-pll and some are controlled by FHCTL.
>
> Another question: is this also changing on a per-board basis?
>
> (note: the pll names in the example are random and not specific to anything)
>
> Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
>                   NNAPLL, MFGPLL
>           board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
>
> Granted that the two A, B boards are using the same SoC, can that ever happen?
>
> > And use `perms` param to decide
> > whether a PLL is using FHCTL to change its frequency.
>
> The perms param seems to be about:
>   * Enabling debug (but you're not providing any way to actually use debugging
>     features, so what's the point?)
>   * Handing over PLL control to FHCTL for hopping (can be as well done with
>     simply using a different .set_rate() callback instead of a flag)
>   * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
>     legit use for flags, but if it's just one flag, you can as well use a
>     bool and manage this with a devicetree param like "enable-ssc")
>
> That said, I think that the current way of enabling the FHCTL is more complicated
> than how it should really be.
>
> >
> > 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.
> >
>
> Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
> so much... it's really just a few register writes and nothing else, so I really
> don't see where the problem is, here.
>
> Another issue is that this driver may be largely incomplete, so perhaps I can't
> really see the complications you're talking about? Is this the case?
>
> Regarding keeping the FHCTL code in separated files, that's fine, but I would still
> integrate it tightly in clk-pll and its registration flow, because - yes, this is
> for sure not mandatory, but the main parameters are constant, they never change for
> a specific PLL, as they're register offsets, bits and masks (which, again, will
> never change as long as we're using the same SoC).
>
> >>
> >> 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?
> >
>
> If it's important that the hardware has frddsx_en, sfstrx_en, fhctlx_en *before*
> programming df_val/dt_val, then yes.... otherwise, just use writel_relaxed()
> everywhere until the last write has to happen.
>
> writel_relaxed(something,       a)
> writel_relaxed(something_else,  b)
> writel_relaxed(something_more,  a)
> writel_relaxed(another_one,     c)
> writel_relaxed(blah,            b)
> writel(reg_cfg,                 X)
>
> ....but having a second look at it, this doesn't make a lot of sense because you
> are anyway performing multiple writes to the same `reg_cfg`, so I think that you
> can as well aggregate the writes in one and reduce the barriers like that, so

Writes to the same endpoint are always ordered. The barrier gives you
ordering against writes to memory _only_.

MMIO addresses are mapped as Device-nGnRE. nR specifically means
non-reordering.

If you want to make sure writes hit the hardware, instead of getting
queued up in the interconnects, you need to read back the register
from the hardware.

For reference, see this talk: https://youtu.be/i6DayghhA8Q
Note: I banged my head against this more than a couple times.

ChenYu

> we'd have something like:
>
>
> u32 pcw_val, val;
>
> /* Important: This assumes that the contents of reg_cfg never change during the
>     execution of this programming sequence. */
> val = readl_relaxed(regs->reg_cfg);
> pcw_val = readl_relaxed(regs->reg_con_pcw) & data->dds_mask;
>
> /* Pause/disable Frequency Hopping controller for reconfiguration */
>
>
> /* P.S.: can't we use FHCTLx_PAUSE instead of turning off?? */
>
>
>
> /* SSC: Disable free-run mode */
> val &= ~data->frddsx_en;
>
> /* Disable Soft-start mode */
> val &= ~data->sfstrx_en;
>
> /* Disable Frequency Hopping controller */
> val &= ~data->fhctlx_en;
>
> writel(val, regs->reg_cfg);
>
> /* **** warning: I'm covering only the enablement flow, not the disablement **** */
>
> /* SSC Slope: Set delta frequency, delta time (df/dt) */
> val |= data->df_val & data->msk_frddsx_dys;
> val |= data->dt_val & data->msk_frddsx_dts;
>
> /* is it important to write these before DDS?
>   * no -> writel_relaxed; yes -> writel
>   */
> writel_relaxed(val, regs->reg_cfg);
>
> /* Update PLL Toggle value */
> writel_relaxed(pcw_val | data->tgl_org, regs->reg_dds);
>
> /* SSC Swing: Calculate upper/lower limits */
> updnlmt_val = PERCENT_TO_DDSLMT((readl_relaxed(regs->reg_dds) & data->dds_mask),
>                                 rate << data->updnlmt_shft);
> writel_relaxed(updnlmt_val, regs->reg_updnlmt);
>
> /* Hand over PLL control to FHCTL */
> fh_set_field(regs->reg_hp_en, BIT(fh_id), 1);
>
> /* Re-Enable SSC and Hopping control */
> val |= data->frddsx_en | data->fhctlx_en;
> writel(val, data->reg_cfg);
>
>
> Roughly, that's the idea.
> Also, keep in mind that aggregating the writes when possible is already
> improving the flow... relaxing R/W is another improvement though... but
> beware that technically this is important only in performance paths (so
> if this function gets called only very few times in a kernel life, it's
> not really important to use _relaxed accessors).
>
> Besides... I don't *really* like seeing the fh_{set, get}_field helpers...
> they're confusing at best, and open-coding the R/W makes you able to
> aggregate fields in one write without impacting on human readability.
>
>
> >>
> >> 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.
> >
>
> Uhm, I don't think that `struct clk_div_table` is tied to individual clk dividers
> in its definition... I mean, it shouldn't be a problem to reuse it in this case...
> The advantage of it is that we are able to set a clear idx<->divider relation.
>
> Anyway, if you have strong feelings about not using clk_div_table, it's ok,
> unless anyone else has considerations about that.
>
Edward-JW Yang July 6, 2022, 1:07 p.m. UTC | #5
On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
> <angelogioacchino.delregno@collabora.com> wrote:
> > 
> > Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> > > 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@mediatek.com>
> > > > > Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> > > > 
> > > > 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.
> > 
> > I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
> > In any case, this not being a *mandatory* component doesn't mean that when it is
> > enabled it's not changing the way we manage the PLLs..........
> > 
> > > 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
> > 
> > I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
> > APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
> > the set_rate() logic of clk-pll.

Do you mean we need to drop the current set_rate() logic (direct register write) and 
use Frequency Hopping Controller instead?

I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
HW.
So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
handle this two types of PLL.

> > 
> > > current set_rate method in clk-pll changes PLL register setting directly. Another
> > > way uses FHCTL to change PLL rate.
> > 
> > ...and of course, if we change that, we're effectively mutating the functionality
> > of the MediaTek clk-pll driver and please understand that seeing a clear mutation
> > in that driver is a bit more human-readable.
> > 
> > Besides, this makes me think about one question: is there any instance in which,
> > when FHCTL rate setting fails, we fall back to direct register writes?
> > 
> > I don't think that this is feasible because we have a register in FHCTL that
> > effectively hands over control to it, so direct register writes should not work
> > when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
> > sure that my understanding is right.

It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
control mode will switch back to APMIXEDSYS after frequency hopping completed.

There are two cases that we need to fall back to direct register writes:
  1. PLL support FHCTL but it doesn't want to use FHCTL.
  2. PLL doesn't support FHCTL HW.

> > 
> > > We will set some PLL's frequency be controlled
> > > by clk-pll and some are controlled by FHCTL.
> > 
> > Another question: is this also changing on a per-board basis?
> > 
> > (note: the pll names in the example are random and not specific to anything)
> > 
> > Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
> >                   NNAPLL, MFGPLL
> >           board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
> > 
> > Granted that the two A, B boards are using the same SoC, can that ever happen?

This could happen if A, B boards have different desense issue.

> > 
> > > And use `perms` param to decide
> > > whether a PLL is using FHCTL to change its frequency.
> > 
> > The perms param seems to be about:
> >   * Enabling debug (but you're not providing any way to actually use debugging
> >     features, so what's the point?)

Debugging feature is not used yet, we can removed it.

> >   * Handing over PLL control to FHCTL for hopping (can be as well done with
> >     simply using a different .set_rate() callback instead of a flag)

There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
this case.

> >   * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
> >     legit use for flags, but if it's just one flag, you can as well use a
> >     bool and manage this with a devicetree param like "enable-ssc")
> > 
> > That said, I think that the current way of enabling the FHCTL is more complicated
> > than how it should really be.

Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
these two are per-board basis.

We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
FHCTL and not all PLLs have need of using FHCTL-hopping.

> > 
> > > 
> > > 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.
> > > 
> > 
> > Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
> > so much... it's really just a few register writes and nothing else, so I really
> > don't see where the problem is, here.
> > 
> > Another issue is that this driver may be largely incomplete, so perhaps I can't
> > really see the complications you're talking about? Is this the case?
> > 
> > Regarding keeping the FHCTL code in separated files, that's fine, but I would still
> > integrate it tightly in clk-pll and its registration flow, because - yes, this is
> > for sure not mandatory, but the main parameters are constant, they never change for
> > a specific PLL, as they're register offsets, bits and masks (which, again, will
> > never change as long as we're using the same SoC).

The driver may need to supoport microP by future HW design, standalone file clk-
fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
and those different init-flow also need to run some communication API with microP.
Those communication APIs are not suitable to merge into clk-pll.

> > 
> > > > 
> > > > 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?
> > > 
> > 
> > If it's important that the hardware has frddsx_en, sfstrx_en, fhctlx_en *before*
> > programming df_val/dt_val, then yes.... otherwise, just use writel_relaxed()
> > everywhere until the last write has to happen.
> > 
> > writel_relaxed(something,       a)
> > writel_relaxed(something_else,  b)
> > writel_relaxed(something_more,  a)
> > writel_relaxed(another_one,     c)
> > writel_relaxed(blah,            b)
> > writel(reg_cfg,                 X)
> > 
> > ....but having a second look at it, this doesn't make a lot of sense because you
> > are anyway performing multiple writes to the same `reg_cfg`, so I think that you
> > can as well aggregate the writes in one and reduce the barriers like that, so
> 
> Writes to the same endpoint are always ordered. The barrier gives you
> ordering against writes to memory _only_.
> 
> MMIO addresses are mapped as Device-nGnRE. nR specifically means
> non-reordering.
> 
> If you want to make sure writes hit the hardware, instead of getting
> queued up in the interconnects, you need to read back the register
> from the hardware.
> 
> For reference, see this talk: https://urldefense.com/v3/__https://youtu.be/i6DayghhA8Q__;!!CTRNKA9wMg0ARbw!yxO58UINgJKmjJDMFJoDC40Y_B9sd6L5xnumLLgXJMaj1z0tnQMXw-fv-cEzghuy1cPSXKc$ 
> Note: I banged my head against this more than a couple times.
> 
> ChenYu

Thanks for example.
Confirm with our HW design, frddsx_en, sfsrx_en, fhctlx_en must to set by order. We
will use writel here instead of writel_relaxed.

> 
> > we'd have something like:
> > 
> > 
> > u32 pcw_val, val;
> > 
> > /* Important: This assumes that the contents of reg_cfg never change during the
> >     execution of this programming sequence. */
> > val = readl_relaxed(regs->reg_cfg);
> > pcw_val = readl_relaxed(regs->reg_con_pcw) & data->dds_mask;
> > 
> > /* Pause/disable Frequency Hopping controller for reconfiguration */
> > 
> > 
> > /* P.S.: can't we use FHCTLx_PAUSE instead of turning off?? */

Should disable Frequency Hopping controller before configuration.

> > 
> > 
> > 
> > /* SSC: Disable free-run mode */
> > val &= ~data->frddsx_en;
> > 
> > /* Disable Soft-start mode */
> > val &= ~data->sfstrx_en;
> > 
> > /* Disable Frequency Hopping controller */
> > val &= ~data->fhctlx_en;
> > 
> > writel(val, regs->reg_cfg);
> > 
> > /* **** warning: I'm covering only the enablement flow, not the disablement **** */
> > 
> > /* SSC Slope: Set delta frequency, delta time (df/dt) */
> > val |= data->df_val & data->msk_frddsx_dys;
> > val |= data->dt_val & data->msk_frddsx_dts;
> > 
> > /* is it important to write these before DDS?
> >   * no -> writel_relaxed; yes -> writel
> >   */
> > writel_relaxed(val, regs->reg_cfg);
> > 
> > /* Update PLL Toggle value */
> > writel_relaxed(pcw_val | data->tgl_org, regs->reg_dds);
> > 
> > /* SSC Swing: Calculate upper/lower limits */
> > updnlmt_val = PERCENT_TO_DDSLMT((readl_relaxed(regs->reg_dds) & data->dds_mask),
> >                                 rate << data->updnlmt_shft);
> > writel_relaxed(updnlmt_val, regs->reg_updnlmt);
> > 
> > /* Hand over PLL control to FHCTL */
> > fh_set_field(regs->reg_hp_en, BIT(fh_id), 1);
> > 
> > /* Re-Enable SSC and Hopping control */
> > val |= data->frddsx_en | data->fhctlx_en;
> > writel(val, data->reg_cfg);
> > 
> > 
> > Roughly, that's the idea.
> > Also, keep in mind that aggregating the writes when possible is already
> > improving the flow... relaxing R/W is another improvement though... but
> > beware that technically this is important only in performance paths (so
> > if this function gets called only very few times in a kernel life, it's
> > not really important to use _relaxed accessors).
> > 
> > Besides... I don't *really* like seeing the fh_{set, get}_field helpers...
> > they're confusing at best, and open-coding the R/W makes you able to
> > aggregate fields in one write without impacting on human readability.

We will change fh_{set, get}_filed helpers to writel, readl. Thanks.

> > 
> > 
> > > > 
> > > > 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.
> > > 
> > 
> > Uhm, I don't think that `struct clk_div_table` is tied to individual clk dividers
> > in its definition... I mean, it shouldn't be a problem to reuse it in this case...
> > The advantage of it is that we are able to set a clear idx<->divider relation.
> > 
> > Anyway, if you have strong feelings about not using clk_div_table, it's ok,
> > unless anyone else has considerations about that.
> > 

We still want to use postdiv_table. Thanks.
AngeloGioacchino Del Regno July 14, 2022, 11:04 a.m. UTC | #6
Il 06/07/22 15:07, Edward-JW Yang ha scritto:
> On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
>> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
>> <angelogioacchino.delregno@collabora.com> wrote:
>>>
>>> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
>>>> 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@mediatek.com>
>>>>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
>>>>>
>>>>> 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.
>>>
>>> I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
>>> In any case, this not being a *mandatory* component doesn't mean that when it is
>>> enabled it's not changing the way we manage the PLLs..........
>>>
>>>> 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
>>>
>>> I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
>>> APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
>>> the set_rate() logic of clk-pll.
> 
> Do you mean we need to drop the current set_rate() logic (direct register write) and
> use Frequency Hopping Controller instead?
> 

On PLLs that are supported by the Frequency Hopping controller, yes: we should
simply use a different .set_rate() callback in clk-pll.c, and we should return
a failure if the FHCTL fails to set the rate - so we should *not* fall back to
direct register writes, as on some platforms and in some conditions, using
direct register writes (which means that we skip FHCTL), may lead to unstable
system.

This means that we need logic such that, in mtk_clk_register_pll(), we end up
having something like that:

if (fhctl_is_enabled(pll))
	init.ops = &mtk_pll_fhctl_ops;
else
	init.ops = &mtk_pll_ops;

> I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
> choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
> HW.

Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
that such PLL can be managed by FHCTL, for example:

	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,

	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),

becomes

	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,

	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);

where 'true' means "FHCTL is supported".

Then, we register the PLLs with something like:

mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);

...where fhctl_register_version is used to assign the right fhctl register offsets.
Also, it's not needed to assign all of the register offsets statically, because
they can be easily calculated based on the number of supported PLLs, since the
registers are structured like

[FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
[FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON

[FHCTL PER-PLL REGISTERS] <--- CFG...MON
^^^ where this is repeated X times for X PLLs.

so, keeping the example of MT8186, we can get the per-pll register like:

#define FHCTL_PLL_OFFSET	0x3c
#define FHCTL_PLL_LEN		0x14

#define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
#define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
#define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)

we don't need to put all of them in a structure and for each PLL.

> So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
> handle this two types of PLL.
> 

As already said, we preventively know which PLLs support FHCTL and which does not,
so we can use a different .set_rate() callback.

>>>
>>>> current set_rate method in clk-pll changes PLL register setting directly. Another
>>>> way uses FHCTL to change PLL rate.
>>>
>>> ...and of course, if we change that, we're effectively mutating the functionality
>>> of the MediaTek clk-pll driver and please understand that seeing a clear mutation
>>> in that driver is a bit more human-readable.
>>>
>>> Besides, this makes me think about one question: is there any instance in which,
>>> when FHCTL rate setting fails, we fall back to direct register writes?
>>>
>>> I don't think that this is feasible because we have a register in FHCTL that
>>> effectively hands over control to it, so direct register writes should not work
>>> when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
>>> sure that my understanding is right.
> 
> It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
> control mode will switch back to APMIXEDSYS after frequency hopping completed.
> 
> There are two cases that we need to fall back to direct register writes:
>    1. PLL support FHCTL but it doesn't want to use FHCTL.
>    2. PLL doesn't support FHCTL HW.
> 

For case N.1, if this is board-specific, we have to resort to devicetree properties
that will enable/disable FHCTL on specific PLLs.

mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;

mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;

These are just examples - I don't currently know if it's a better idea to have an
allowlist or a blocklist as devicetree properties, as that depends on the expected
number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).

>>>
>>>> We will set some PLL's frequency be controlled
>>>> by clk-pll and some are controlled by FHCTL.
>>>
>>> Another question: is this also changing on a per-board basis?
>>>
>>> (note: the pll names in the example are random and not specific to anything)
>>>
>>> Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
>>>                    NNAPLL, MFGPLL
>>>            board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
>>>
>>> Granted that the two A, B boards are using the same SoC, can that ever happen?
> 
> This could happen if A, B boards have different desense issue.
> 

Ok, so it's definitely board specific. Devicetree is the way to go for this.

>>>
>>>> And use `perms` param to decide
>>>> whether a PLL is using FHCTL to change its frequency.
>>>
>>> The perms param seems to be about:
>>>    * Enabling debug (but you're not providing any way to actually use debugging
>>>      features, so what's the point?)
> 
> Debugging feature is not used yet, we can removed it.
> 

If the debugging features of the FHCTL driver will be like what I can see on
the downstream MT6893 5.10 kernel, that's not really applicable to upstream.

In that case, please remove the debug.

>>>    * Handing over PLL control to FHCTL for hopping (can be as well done with
>>>      simply using a different .set_rate() callback instead of a flag)
> 
> There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
> this case.
> 

Use the flag to set the right .set_rate() callback, set at probe time, instead of
checking that flag at every set_rate() call.

>>>    * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
>>>      legit use for flags, but if it's just one flag, you can as well use a
>>>      bool and manage this with a devicetree param like "enable-ssc")
>>>
>>> That said, I think that the current way of enabling the FHCTL is more complicated
>>> than how it should really be.
> 
> Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
> these two are per-board basis.
> 
> We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
> FHCTL and not all PLLs have need of using FHCTL-hopping.
> 

Board specific -> devicetree

SoC specific -> hardcode, no devicetree.

>>>
>>>>
>>>> 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.
>>>>
>>>
>>> Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
>>> so much... it's really just a few register writes and nothing else, so I really
>>> don't see where the problem is, here.
>>>
>>> Another issue is that this driver may be largely incomplete, so perhaps I can't
>>> really see the complications you're talking about? Is this the case?
>>>
>>> Regarding keeping the FHCTL code in separated files, that's fine, but I would still
>>> integrate it tightly in clk-pll and its registration flow, because - yes, this is
>>> for sure not mandatory, but the main parameters are constant, they never change for
>>> a specific PLL, as they're register offsets, bits and masks (which, again, will
>>> never change as long as we're using the same SoC).
> 
> The driver may need to supoport microP by future HW design, standalone file clk-
> fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
> and those different init-flow also need to run some communication API with microP.
> Those communication APIs are not suitable to merge into clk-pll.
> 

Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
the microP in the SoC-specific clock drivers, I think that's not a problem?

clk_mtfuturesoc_someip_probe()
{
	.... register clocks ....

	freqhopping_microp_init();

	return ret;
}

If there's hardware out there that supports such feature and a downstream kernel to
look at, please tell me which one, so that I will be able to check it out and
perhaps understand how this flow works.

P.S.: I guess it's not fhctl-sspm?

Regards,
Angelo
Boris Lysov July 15, 2022, 12:34 a.m. UTC | #7
On Thu, 14 Jul 2022 13:04:49 +0200
AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> wrote:

> Il 06/07/22 15:07, Edward-JW Yang ha scritto:
> > On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
> >> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
> >> <angelogioacchino.delregno@collabora.com> wrote:
> >>>
> >>> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> >>>> 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@mediatek.com>
> >>>>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> >>>>>
> >>>>> 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.
> >>>
> >>> I know what FHCTL is, but thank you anyway for the explanation, that's
> >>> appreciated. In any case, this not being a *mandatory* component doesn't
> >>> mean that when it is enabled it's not changing the way we manage the
> >>> PLLs..........
> >>>
> >>>> 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
> >>>
> >>> I disagree: when we want to use FHCTL, we effectively hand-over PLL
> >>> control from APMIXEDSYS to the Frequency Hopping controller - and we're
> >>> effectively replacing the set_rate() logic of clk-pll.
> > 
> > Do you mean we need to drop the current set_rate() logic (direct register
> > write) and use Frequency Hopping Controller instead?
> > 
> 
> On PLLs that are supported by the Frequency Hopping controller, yes: we should
> simply use a different .set_rate() callback in clk-pll.c, and we should return
> a failure if the FHCTL fails to set the rate - so we should *not* fall back to
> direct register writes, as on some platforms and in some conditions, using
> direct register writes (which means that we skip FHCTL), may lead to unstable
> system.
> 
> This means that we need logic such that, in mtk_clk_register_pll(), we end up
> having something like that:
> 
> if (fhctl_is_enabled(pll))
> 	init.ops = &mtk_pll_fhctl_ops;
> else
> 	init.ops = &mtk_pll_ops;

Looks like accepting my patch [1] wouldn't be a bad idea, after all.
[1] https://lists.infradead.org/pipermail/linux-mediatek/2022-May/041293.html
Edward-JW Yang July 20, 2022, 1:51 p.m. UTC | #8
Hi AngeloGioacchino,

Thanks for all the advices and examples.

On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
> Il 06/07/22 15:07, Edward-JW Yang ha scritto:
> > On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
> > > On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
> > > <angelogioacchino.delregno@collabora.com> wrote:
> > > > 
> > > > Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> > > > > 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@mediatek.com>
> > > > > > > Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> > > > > > 
> > > > > > 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.
> > > > 
> > > > I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
> > > > In any case, this not being a *mandatory* component doesn't mean that when it is
> > > > enabled it's not changing the way we manage the PLLs..........
> > > > 
> > > > > 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
> > > > 
> > > > I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
> > > > APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
> > > > the set_rate() logic of clk-pll.
> > 
> > Do you mean we need to drop the current set_rate() logic (direct register write) and
> > use Frequency Hopping Controller instead?
> > 
> 
> On PLLs that are supported by the Frequency Hopping controller, yes: we should
> simply use a different .set_rate() callback in clk-pll.c, and we should return
> a failure if the FHCTL fails to set the rate - so we should *not* fall back to
> direct register writes, as on some platforms and in some conditions, using
> direct register writes (which means that we skip FHCTL), may lead to unstable
> system.
> 
> This means that we need logic such that, in mtk_clk_register_pll(), we end up
> having something like that:
> 
> if (fhctl_is_enabled(pll))
> 	init.ops = &mtk_pll_fhctl_ops;
> else
> 	init.ops = &mtk_pll_ops;
> 
> > I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
> > choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
> > HW.
> 
> Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
> that such PLL can be managed by FHCTL, for example:
> 
> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> 
> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
> 
> becomes
> 
> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> 
> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
> 
> where 'true' means "FHCTL is supported".

Does it still have an independent FHCTL driver after modifying to this? From your example,
setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
clk-pll and become part of clk-pll driver.

We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
callback function instead of whole clk_ops. The boot-up sequence is like:

1. clk-pll + clk dts
	probe  -> clk-pll original flow, nothing to change

        /* clk-pll provide multation API for set_rate */
	/* mutate necessary set_rate() instead of mutating all ops */
		def register_fhctl_set_rate(pll_name, callback)
			ops = find_pll_ops_by_name(pll_name)
			log("change set_rate to fhctl callback for $pll_name")
			ops->set_rate = callback

2. FHCTL driver + fhctl dts
	probe
		options = parsing dts (board specific, hopping disalbe or ssc-rate)
		init FHCTL HW
		for PLL in dts
			if (ssc-rate > 0)
				enable_ssc(ssc-rate)
			if (hop-enabled)
				/* mutate CCF set_rate, FHCTL engaged CCF */
				register_fhctl_CCF(pll_name, callback)

> 
> Then, we register the PLLs with something like:
> 
> mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
> 
> ...where fhctl_register_version is used to assign the right fhctl register offsets.
> Also, it's not needed to assign all of the register offsets statically, because
> they can be easily calculated based on the number of supported PLLs, since the
> registers are structured like
> 
> [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
> [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
> 
> [FHCTL PER-PLL REGISTERS] <--- CFG...MON
> ^^^ where this is repeated X times for X PLLs.
> 
> so, keeping the example of MT8186, we can get the per-pll register like:
> 
> #define FHCTL_PLL_OFFSET	0x3c
> #define FHCTL_PLL_LEN		0x14
> 
> #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
> #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
> #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
> 
> we don't need to put all of them in a structure and for each PLL.

We use structure instead of using macros is because the register offset may have
difference between ICs. If we use macro, we need to maintain different versions of macros.
Using structure to store these register offsets is more flexible.

> 
> > So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
> > handle this two types of PLL.
> > 
> 
> As already said, we preventively know which PLLs support FHCTL and which does not,
> so we can use a different .set_rate() callback.

Ok, we can use a different .set_rate() callback when fhctl driver probing.

> 
> > > > 
> > > > > current set_rate method in clk-pll changes PLL register setting directly. Another
> > > > > way uses FHCTL to change PLL rate.
> > > > 
> > > > ...and of course, if we change that, we're effectively mutating the functionality
> > > > of the MediaTek clk-pll driver and please understand that seeing a clear mutation
> > > > in that driver is a bit more human-readable.
> > > > 
> > > > Besides, this makes me think about one question: is there any instance in which,
> > > > when FHCTL rate setting fails, we fall back to direct register writes?
> > > > 
> > > > I don't think that this is feasible because we have a register in FHCTL that
> > > > effectively hands over control to it, so direct register writes should not work
> > > > when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
> > > > sure that my understanding is right.
> > 
> > It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
> > control mode will switch back to APMIXEDSYS after frequency hopping completed.
> > 
> > There are two cases that we need to fall back to direct register writes:
> >    1. PLL support FHCTL but it doesn't want to use FHCTL.
> >    2. PLL doesn't support FHCTL HW.
> > 
> 
> For case N.1, if this is board-specific, we have to resort to devicetree properties
> that will enable/disable FHCTL on specific PLLs.
> 
> mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
> 
> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
> 
> These are just examples - I don't currently know if it's a better idea to have an
> allowlist or a blocklist as devicetree properties, as that depends on the expected
> number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
> enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).

We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.

> 
> > > > 
> > > > > We will set some PLL's frequency be controlled
> > > > > by clk-pll and some are controlled by FHCTL.
> > > > 
> > > > Another question: is this also changing on a per-board basis?
> > > > 
> > > > (note: the pll names in the example are random and not specific to anything)
> > > > 
> > > > Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
> > > >                    NNAPLL, MFGPLL
> > > >            board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
> > > > 
> > > > Granted that the two A, B boards are using the same SoC, can that ever happen?
> > 
> > This could happen if A, B boards have different desense issue.
> > 
> 
> Ok, so it's definitely board specific. Devicetree is the way to go for this.
> 
> > > > 
> > > > > And use `perms` param to decide
> > > > > whether a PLL is using FHCTL to change its frequency.
> > > > 
> > > > The perms param seems to be about:
> > > >    * Enabling debug (but you're not providing any way to actually use debugging
> > > >      features, so what's the point?)
> > 
> > Debugging feature is not used yet, we can removed it.
> > 
> 
> If the debugging features of the FHCTL driver will be like what I can see on
> the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
> 
> In that case, please remove the debug.

Ok, we will remove it.

> 
> > > >    * Handing over PLL control to FHCTL for hopping (can be as well done with
> > > >      simply using a different .set_rate() callback instead of a flag)
> > 
> > There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
> > this case.
> > 
> 
> Use the flag to set the right .set_rate() callback, set at probe time, instead of
> checking that flag at every set_rate() call.

We will setup .set_rate() callback when doing fhctl-pll init.

> 
> > > >    * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
> > > >      legit use for flags, but if it's just one flag, you can as well use a
> > > >      bool and manage this with a devicetree param like "enable-ssc")
> > > > 
> > > > That said, I think that the current way of enabling the FHCTL is more complicated
> > > > than how it should really be.
> > 
> > Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
> > these two are per-board basis.
> > 
> > We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
> > FHCTL and not all PLLs have need of using FHCTL-hopping.
> > 
> 
> Board specific -> devicetree
> 
> SoC specific -> hardcode, no devicetree.
> 
> > > > 
> > > > > 
> > > > > 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.
> > > > > 
> > > > 
> > > > Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
> > > > so much... it's really just a few register writes and nothing else, so I really
> > > > don't see where the problem is, here.
> > > > 
> > > > Another issue is that this driver may be largely incomplete, so perhaps I can't
> > > > really see the complications you're talking about? Is this the case?
> > > > 
> > > > Regarding keeping the FHCTL code in separated files, that's fine, but I would still
> > > > integrate it tightly in clk-pll and its registration flow, because - yes, this is
> > > > for sure not mandatory, but the main parameters are constant, they never change for
> > > > a specific PLL, as they're register offsets, bits and masks (which, again, will
> > > > never change as long as we're using the same SoC).
> > 
> > The driver may need to supoport microP by future HW design, standalone file clk-
> > fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
> > and those different init-flow also need to run some communication API with microP.
> > Those communication APIs are not suitable to merge into clk-pll.
> > 
> 
> Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
> the microP in the SoC-specific clock drivers, I think that's not a problem?
> 
> clk_mtfuturesoc_someip_probe()
> {
> 	.... register clocks ....
> 
> 	freqhopping_microp_init();
> 
> 	return ret;
> }
> 
> If there's hardware out there that supports such feature and a downstream kernel to
> look at, please tell me which one, so that I will be able to check it out and
> perhaps understand how this flow works.
> 
> P.S.: I guess it's not fhctl-sspm?

You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
kernel. Those codes require the PLL hardware specification to determine which PLL
group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
communicate with the microP.

If we implement these things into clk-pll driver, clk-pll driver not only needs to control
PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
works that is not belong to PLL operation. That's why we tend to have a standalone driver
for FHCTL.

> 
> Regards,
> Angelo
AngeloGioacchino Del Regno July 21, 2022, 9:43 a.m. UTC | #9
Il 20/07/22 15:51, Edward-JW Yang ha scritto:
> Hi AngeloGioacchino,
> 
> Thanks for all the advices and examples.
> 
> On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
>> Il 06/07/22 15:07, Edward-JW Yang ha scritto:
>>> On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
>>>> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
>>>> <angelogioacchino.delregno@collabora.com> wrote:
>>>>>
>>>>> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
>>>>>> 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@mediatek.com>
>>>>>>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
>>>>>>>
>>>>>>> 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.
>>>>>
>>>>> I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
>>>>> In any case, this not being a *mandatory* component doesn't mean that when it is
>>>>> enabled it's not changing the way we manage the PLLs..........
>>>>>
>>>>>> 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
>>>>>
>>>>> I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
>>>>> APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
>>>>> the set_rate() logic of clk-pll.
>>>
>>> Do you mean we need to drop the current set_rate() logic (direct register write) and
>>> use Frequency Hopping Controller instead?
>>>
>>
>> On PLLs that are supported by the Frequency Hopping controller, yes: we should
>> simply use a different .set_rate() callback in clk-pll.c, and we should return
>> a failure if the FHCTL fails to set the rate - so we should *not* fall back to
>> direct register writes, as on some platforms and in some conditions, using
>> direct register writes (which means that we skip FHCTL), may lead to unstable
>> system.
>>
>> This means that we need logic such that, in mtk_clk_register_pll(), we end up
>> having something like that:
>>
>> if (fhctl_is_enabled(pll))
>> 	init.ops = &mtk_pll_fhctl_ops;
>> else
>> 	init.ops = &mtk_pll_ops;
>>
>>> I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
>>> choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
>>> HW.
>>
>> Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
>> that such PLL can be managed by FHCTL, for example:
>>
>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>
>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
>>
>> becomes
>>
>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>
>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
>>
>> where 'true' means "FHCTL is supported".
> 
> Does it still have an independent FHCTL driver after modifying to this? From your example,
> setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
> clk-pll and become part of clk-pll driver.
> 

The direct-MMIO part of FHCTL becomes part of the clk-pll driver, yes - but then
I also find it unacceptable to embed the IPI communication inside of there, so we
can have an "external" helper for that.


> We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
> callback function instead of whole clk_ops. The boot-up sequence is like:
> 
> 1. clk-pll + clk dts
> 	probe  -> clk-pll original flow, nothing to change
> 
>          /* clk-pll provide multation API for set_rate */
> 	/* mutate necessary set_rate() instead of mutating all ops */
> 		def register_fhctl_set_rate(pll_name, callback)
> 			ops = find_pll_ops_by_name(pll_name)
> 			log("change set_rate to fhctl callback for $pll_name")
> 			ops->set_rate = callback
> 
> 2. FHCTL driver + fhctl dts
> 	probe
> 		options = parsing dts (board specific, hopping disalbe or ssc-rate)
> 		init FHCTL HW
> 		for PLL in dts
> 			if (ssc-rate > 0)
> 				enable_ssc(ssc-rate)
> 			if (hop-enabled)
> 				/* mutate CCF set_rate, FHCTL engaged CCF */
> 				register_fhctl_CCF(pll_name, callback)
> 

I really don't like having PLL names in devicetree: they're already defined in
clock drivers and they will change on a per-SoC basis - and we do have per-SoC
drivers...

Whatever goes to devicetree should be something that we need to vary on a
per-board/platform(project) basis, so, enablement of FHCTL per-pll (by using
handles and numeral bindings as per the example that I previously wrote),
enablement of spread spectrum and its rate... and nothing else.

>>
>> Then, we register the PLLs with something like:
>>
>> mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
>>
>> ...where fhctl_register_version is used to assign the right fhctl register offsets.
>> Also, it's not needed to assign all of the register offsets statically, because
>> they can be easily calculated based on the number of supported PLLs, since the
>> registers are structured like
>>
>> [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
>> [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
>>
>> [FHCTL PER-PLL REGISTERS] <--- CFG...MON
>> ^^^ where this is repeated X times for X PLLs.
>>
>> so, keeping the example of MT8186, we can get the per-pll register like:
>>
>> #define FHCTL_PLL_OFFSET	0x3c
>> #define FHCTL_PLL_LEN		0x14
>>
>> #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
>> #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
>> #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
>>
>> we don't need to put all of them in a structure and for each PLL.
> 
> We use structure instead of using macros is because the register offset may have
> difference between ICs. If we use macro, we need to maintain different versions of macros.
> Using structure to store these register offsets is more flexible.
> 

I understand. What I don't like about your specific approach is the amount of
register offsets that we store in that structure, looks like it's a bit too many.

I've seen that there's a common pattern at least by checking downstream 5.10 and
MT8186/95 layouts, so I still think that using these macros will be beneficial.

We can always add parameters to the structure in a later commit: in my opinion,
that will help to engineer a better, shorter, cleaner solution for calculating
these registers anyway... but I will leave this choice to you, anyway, since you
know about way more SoCs than I do.

>>
>>> So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
>>> handle this two types of PLL.
>>>
>>
>> As already said, we preventively know which PLLs support FHCTL and which does not,
>> so we can use a different .set_rate() callback.
> 
> Ok, we can use a different .set_rate() callback when fhctl driver probing.
> 
>>
>>>>>
>>>>>> current set_rate method in clk-pll changes PLL register setting directly. Another
>>>>>> way uses FHCTL to change PLL rate.
>>>>>
>>>>> ...and of course, if we change that, we're effectively mutating the functionality
>>>>> of the MediaTek clk-pll driver and please understand that seeing a clear mutation
>>>>> in that driver is a bit more human-readable.
>>>>>
>>>>> Besides, this makes me think about one question: is there any instance in which,
>>>>> when FHCTL rate setting fails, we fall back to direct register writes?
>>>>>
>>>>> I don't think that this is feasible because we have a register in FHCTL that
>>>>> effectively hands over control to it, so direct register writes should not work
>>>>> when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
>>>>> sure that my understanding is right.
>>>
>>> It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
>>> control mode will switch back to APMIXEDSYS after frequency hopping completed.
>>>
>>> There are two cases that we need to fall back to direct register writes:
>>>     1. PLL support FHCTL but it doesn't want to use FHCTL.
>>>     2. PLL doesn't support FHCTL HW.
>>>
>>
>> For case N.1, if this is board-specific, we have to resort to devicetree properties
>> that will enable/disable FHCTL on specific PLLs.
>>
>> mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
>>
>> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
>>
>> These are just examples - I don't currently know if it's a better idea to have an
>> allowlist or a blocklist as devicetree properties, as that depends on the expected
>> number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
>> enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).
> 
> We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
> rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.
> 

Right. For that, we could have a default sensible percentage when SSC is enabled
but no rate is set in devicetree, or we can perhaps consider SSC enabled when any
meaningful SSC rate is set... For example:

mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
mediatek,ssc-percent = <5>, <5>;

... or something like:

mediatek,ssc = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;

...but I'd like to have some feedback on that from somebody else, as I don't know
if that would be acceptable in devicetree, or if there's any cleaner, niftier
solution.

>>
>>>>>
>>>>>> We will set some PLL's frequency be controlled
>>>>>> by clk-pll and some are controlled by FHCTL.
>>>>>
>>>>> Another question: is this also changing on a per-board basis?
>>>>>
>>>>> (note: the pll names in the example are random and not specific to anything)
>>>>>
>>>>> Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
>>>>>                     NNAPLL, MFGPLL
>>>>>             board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
>>>>>
>>>>> Granted that the two A, B boards are using the same SoC, can that ever happen?
>>>
>>> This could happen if A, B boards have different desense issue.
>>>
>>
>> Ok, so it's definitely board specific. Devicetree is the way to go for this.
>>
>>>>>
>>>>>> And use `perms` param to decide
>>>>>> whether a PLL is using FHCTL to change its frequency.
>>>>>
>>>>> The perms param seems to be about:
>>>>>     * Enabling debug (but you're not providing any way to actually use debugging
>>>>>       features, so what's the point?)
>>>
>>> Debugging feature is not used yet, we can removed it.
>>>
>>
>> If the debugging features of the FHCTL driver will be like what I can see on
>> the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
>>
>> In that case, please remove the debug.
> 
> Ok, we will remove it.
> 
>>
>>>>>     * Handing over PLL control to FHCTL for hopping (can be as well done with
>>>>>       simply using a different .set_rate() callback instead of a flag)
>>>
>>> There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
>>> this case.
>>>
>>
>> Use the flag to set the right .set_rate() callback, set at probe time, instead of
>> checking that flag at every set_rate() call.
> 
> We will setup .set_rate() callback when doing fhctl-pll init.
> 
>>
>>>>>     * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
>>>>>       legit use for flags, but if it's just one flag, you can as well use a
>>>>>       bool and manage this with a devicetree param like "enable-ssc")
>>>>>
>>>>> That said, I think that the current way of enabling the FHCTL is more complicated
>>>>> than how it should really be.
>>>
>>> Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
>>> these two are per-board basis.
>>>
>>> We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
>>> FHCTL and not all PLLs have need of using FHCTL-hopping.
>>>
>>
>> Board specific -> devicetree
>>
>> SoC specific -> hardcode, no devicetree.
>>
>>>>>
>>>>>>
>>>>>> 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.
>>>>>>
>>>>>
>>>>> Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
>>>>> so much... it's really just a few register writes and nothing else, so I really
>>>>> don't see where the problem is, here.
>>>>>
>>>>> Another issue is that this driver may be largely incomplete, so perhaps I can't
>>>>> really see the complications you're talking about? Is this the case?
>>>>>
>>>>> Regarding keeping the FHCTL code in separated files, that's fine, but I would still
>>>>> integrate it tightly in clk-pll and its registration flow, because - yes, this is
>>>>> for sure not mandatory, but the main parameters are constant, they never change for
>>>>> a specific PLL, as they're register offsets, bits and masks (which, again, will
>>>>> never change as long as we're using the same SoC).
>>>
>>> The driver may need to supoport microP by future HW design, standalone file clk-
>>> fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
>>> and those different init-flow also need to run some communication API with microP.
>>> Those communication APIs are not suitable to merge into clk-pll.
>>>
>>
>> Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
>> the microP in the SoC-specific clock drivers, I think that's not a problem?
>>
>> clk_mtfuturesoc_someip_probe()
>> {
>> 	.... register clocks ....
>>
>> 	freqhopping_microp_init();
>>
>> 	return ret;
>> }
>>
>> If there's hardware out there that supports such feature and a downstream kernel to
>> look at, please tell me which one, so that I will be able to check it out and
>> perhaps understand how this flow works.
>>
>> P.S.: I guess it's not fhctl-sspm?
> 
> You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
> kernel. Those codes require the PLL hardware specification to determine which PLL
> group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
> communicate with the microP.
> 
> If we implement these things into clk-pll driver, clk-pll driver not only needs to control
> PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
> works that is not belong to PLL operation. That's why we tend to have a standalone driver
> for FHCTL.
> 

Ok having something to analyze made this entire thing a bit more clear in my mind,
thanks for the pointers.

Analyzing clk-fhctl-mcupm and clk-fhctl-gpueb makes me see that there's a lot of
common code between the two: x_hopping_v1(), x_ssc_enable_v1(), x_ssc_disable_v1()
(where x = {gpueb,mcupm}) are really the same functions, duplicated and renamed
and nothing else.
The only difference is the get_xxxx_ipidev(), which is avoidable by assigning
mboxes = <...something...> in devicetree (gpueb mailbox, or mcupm mailbox).

Even the `FH_DEVCTL_CMD_ID` enumeration uses the same values!

To unroll that riddle, I would at that point add a new MediaTek specific clock
driver (like clk-pll) and call it `clk-ipi.c`, because that's what it does in
the end: whatever we do, goes through a mailbox instead of a direct MMIO write.

That clk-fhctl-ipi would contain a probe function that gets the mailbox handle,
then we would add something like `clk_fhctl_set_rate()` function, export it in
the `clk-mtk.h` or in a new `clk-fhctl.h` header, then assign the right callback
in either the SoC's clock driver (by registering a different clock type, which,
in this case, would be clk-fhctl-ipi instead of clk-pll), or in clk-pll itself...

In the end, I'm effectively proposing to:

1. Merge the direct-MMIO handling of FHCTL in clk-pll;
2. Create a new driver (and clock type, eventually) for the IPI handling of FHCTL.

Regards,
Angelo
Edward-JW Yang July 28, 2022, 4:37 a.m. UTC | #10
Hi AngeloGioacchino,

Thanks for the advices.

On Thu, 2022-07-21 at 17:43 +0800, AngeloGioacchino Del Regno wrote:
> Il 20/07/22 15:51, Edward-JW Yang ha scritto:
> > Hi AngeloGioacchino,
> > 
> > Thanks for all the advices and examples.
> > 
> > On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
> > > Il 06/07/22 15:07, Edward-JW Yang ha scritto:
> > > > On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
> > > > > On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
> > > > > <angelogioacchino.delregno@collabora.com> wrote:
> > > > > > 
> > > > > > Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> > > > > > > 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@mediatek.com>
> > > > > > > > > Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> > > > > > > > 
> > > > > > > > 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.
> > > > > > 
> > > > > > I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
> > > > > > In any case, this not being a *mandatory* component doesn't mean that when it is
> > > > > > enabled it's not changing the way we manage the PLLs..........
> > > > > > 
> > > > > > > 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
> > > > > > 
> > > > > > I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
> > > > > > APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
> > > > > > the set_rate() logic of clk-pll.
> > > > 
> > > > Do you mean we need to drop the current set_rate() logic (direct register write) and
> > > > use Frequency Hopping Controller instead?
> > > > 
> > > 
> > > On PLLs that are supported by the Frequency Hopping controller, yes: we should
> > > simply use a different .set_rate() callback in clk-pll.c, and we should return
> > > a failure if the FHCTL fails to set the rate - so we should *not* fall back to
> > > direct register writes, as on some platforms and in some conditions, using
> > > direct register writes (which means that we skip FHCTL), may lead to unstable
> > > system.
> > > 
> > > This means that we need logic such that, in mtk_clk_register_pll(), we end up
> > > having something like that:
> > > 
> > > if (fhctl_is_enabled(pll))
> > > 	init.ops = &mtk_pll_fhctl_ops;
> > > else
> > > 	init.ops = &mtk_pll_ops;
> > > 
> > > > I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
> > > > choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
> > > > HW.
> > > 
> > > Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
> > > that such PLL can be managed by FHCTL, for example:
> > > 
> > > 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> > > 
> > > 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
> > > 
> > > becomes
> > > 
> > > 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> > > 
> > > 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
> > > 
> > > where 'true' means "FHCTL is supported".
> > 
> > Does it still have an independent FHCTL driver after modifying to this? From your example,
> > setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
> > clk-pll and become part of clk-pll driver.
> > 
> 
> The direct-MMIO part of FHCTL becomes part of the clk-pll driver, yes - but then
> I also find it unacceptable to embed the IPI communication inside of there, so we
> can have an "external" helper for that.

I think clk-pll driver should focus on PLL HW itself. Since PLL can work alone without
FHCTL, adding FHCTL control into clk-pll may be a little strange. For this PLL+FHCTL
combination, I want to add a new type of clock driver, like clk-pll-fh. It might be a
easier way to maintain FHCTL HW related changes and won't affect to clk-pll.

> 
> 
> > We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
> > callback function instead of whole clk_ops. The boot-up sequence is like:
> > 
> > 1. clk-pll + clk dts
> > 	probe  -> clk-pll original flow, nothing to change
> > 
> >          /* clk-pll provide multation API for set_rate */
> > 	/* mutate necessary set_rate() instead of mutating all ops */
> > 		def register_fhctl_set_rate(pll_name, callback)
> > 			ops = find_pll_ops_by_name(pll_name)
> > 			log("change set_rate to fhctl callback for $pll_name")
> > 			ops->set_rate = callback
> > 
> > 2. FHCTL driver + fhctl dts
> > 	probe
> > 		options = parsing dts (board specific, hopping disalbe or ssc-rate)
> > 		init FHCTL HW
> > 		for PLL in dts
> > 			if (ssc-rate > 0)
> > 				enable_ssc(ssc-rate)
> > 			if (hop-enabled)
> > 				/* mutate CCF set_rate, FHCTL engaged CCF */
> > 				register_fhctl_CCF(pll_name, callback)
> > 
> 
> I really don't like having PLL names in devicetree: they're already defined in
> clock drivers and they will change on a per-SoC basis - and we do have per-SoC
> drivers...
> 
> Whatever goes to devicetree should be something that we need to vary on a
> per-board/platform(project) basis, so, enablement of FHCTL per-pll (by using
> handles and numeral bindings as per the example that I previously wrote),
> enablement of spread spectrum and its rate... and nothing else.

OK, we will remove PLL names in devicetree.

> 
> > > 
> > > Then, we register the PLLs with something like:
> > > 
> > > mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
> > > 
> > > ...where fhctl_register_version is used to assign the right fhctl register offsets.
> > > Also, it's not needed to assign all of the register offsets statically, because
> > > they can be easily calculated based on the number of supported PLLs, since the
> > > registers are structured like
> > > 
> > > [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
> > > [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
> > > 
> > > [FHCTL PER-PLL REGISTERS] <--- CFG...MON
> > > ^^^ where this is repeated X times for X PLLs.
> > > 
> > > so, keeping the example of MT8186, we can get the per-pll register like:
> > > 
> > > #define FHCTL_PLL_OFFSET	0x3c
> > > #define FHCTL_PLL_LEN		0x14
> > > 
> > > #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
> > > #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
> > > #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
> > > 
> > > we don't need to put all of them in a structure and for each PLL.
> > 
> > We use structure instead of using macros is because the register offset may have
> > difference between ICs. If we use macro, we need to maintain different versions of macros.
> > Using structure to store these register offsets is more flexible.
> > 
> 
> I understand. What I don't like about your specific approach is the amount of
> register offsets that we store in that structure, looks like it's a bit too many.
> 
> I've seen that there's a common pattern at least by checking downstream 5.10 and
> MT8186/95 layouts, so I still think that using these macros will be beneficial.
> 
> We can always add parameters to the structure in a later commit: in my opinion,
> that will help to engineer a better, shorter, cleaner solution for calculating
> these registers anyway... but I will leave this choice to you, anyway, since you
> know about way more SoCs than I do.

OK, we will reduce the structure.

> 
> > > 
> > > > So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
> > > > handle this two types of PLL.
> > > > 
> > > 
> > > As already said, we preventively know which PLLs support FHCTL and which does not,
> > > so we can use a different .set_rate() callback.
> > 
> > Ok, we can use a different .set_rate() callback when fhctl driver probing.
> > 
> > > 
> > > > > > 
> > > > > > > current set_rate method in clk-pll changes PLL register setting directly. Another
> > > > > > > way uses FHCTL to change PLL rate.
> > > > > > 
> > > > > > ...and of course, if we change that, we're effectively mutating the functionality
> > > > > > of the MediaTek clk-pll driver and please understand that seeing a clear mutation
> > > > > > in that driver is a bit more human-readable.
> > > > > > 
> > > > > > Besides, this makes me think about one question: is there any instance in which,
> > > > > > when FHCTL rate setting fails, we fall back to direct register writes?
> > > > > > 
> > > > > > I don't think that this is feasible because we have a register in FHCTL that
> > > > > > effectively hands over control to it, so direct register writes should not work
> > > > > > when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
> > > > > > sure that my understanding is right.
> > > > 
> > > > It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
> > > > control mode will switch back to APMIXEDSYS after frequency hopping completed.
> > > > 
> > > > There are two cases that we need to fall back to direct register writes:
> > > >     1. PLL support FHCTL but it doesn't want to use FHCTL.
> > > >     2. PLL doesn't support FHCTL HW.
> > > > 
> > > 
> > > For case N.1, if this is board-specific, we have to resort to devicetree properties
> > > that will enable/disable FHCTL on specific PLLs.
> > > 
> > > mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
> > > 
> > > mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
> > > 
> > > These are just examples - I don't currently know if it's a better idea to have an
> > > allowlist or a blocklist as devicetree properties, as that depends on the expected
> > > number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
> > > enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).
> > 
> > We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
> > rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.
> > 
> 
> Right. For that, we could have a default sensible percentage when SSC is enabled
> but no rate is set in devicetree, or we can perhaps consider SSC enabled when any
> meaningful SSC rate is set... For example:
> 
> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
> mediatek,ssc-percent = <5>, <5>;
> 
> ... or something like:
> 
> mediatek,ssc = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
> 
> ...but I'd like to have some feedback on that from somebody else, as I don't know
> if that would be acceptable in devicetree, or if there's any cleaner, niftier
> solution.

OK, we will use this:

mediatek,hopping-ssc-percent = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;

> 
> > > 
> > > > > > 
> > > > > > > We will set some PLL's frequency be controlled
> > > > > > > by clk-pll and some are controlled by FHCTL.
> > > > > > 
> > > > > > Another question: is this also changing on a per-board basis?
> > > > > > 
> > > > > > (note: the pll names in the example are random and not specific to anything)
> > > > > > 
> > > > > > Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
> > > > > >                     NNAPLL, MFGPLL
> > > > > >             board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
> > > > > > 
> > > > > > Granted that the two A, B boards are using the same SoC, can that ever happen?
> > > > 
> > > > This could happen if A, B boards have different desense issue.
> > > > 
> > > 
> > > Ok, so it's definitely board specific. Devicetree is the way to go for this.
> > > 
> > > > > > 
> > > > > > > And use `perms` param to decide
> > > > > > > whether a PLL is using FHCTL to change its frequency.
> > > > > > 
> > > > > > The perms param seems to be about:
> > > > > >     * Enabling debug (but you're not providing any way to actually use debugging
> > > > > >       features, so what's the point?)
> > > > 
> > > > Debugging feature is not used yet, we can removed it.
> > > > 
> > > 
> > > If the debugging features of the FHCTL driver will be like what I can see on
> > > the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
> > > 
> > > In that case, please remove the debug.
> > 
> > Ok, we will remove it.
> > 
> > > 
> > > > > >     * Handing over PLL control to FHCTL for hopping (can be as well done with
> > > > > >       simply using a different .set_rate() callback instead of a flag)
> > > > 
> > > > There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
> > > > this case.
> > > > 
> > > 
> > > Use the flag to set the right .set_rate() callback, set at probe time, instead of
> > > checking that flag at every set_rate() call.
> > 
> > We will setup .set_rate() callback when doing fhctl-pll init.
> > 
> > > 
> > > > > >     * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
> > > > > >       legit use for flags, but if it's just one flag, you can as well use a
> > > > > >       bool and manage this with a devicetree param like "enable-ssc")
> > > > > > 
> > > > > > That said, I think that the current way of enabling the FHCTL is more complicated
> > > > > > than how it should really be.
> > > > 
> > > > Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
> > > > these two are per-board basis.
> > > > 
> > > > We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
> > > > FHCTL and not all PLLs have need of using FHCTL-hopping.
> > > > 
> > > 
> > > Board specific -> devicetree
> > > 
> > > SoC specific -> hardcode, no devicetree.
> > > 
> > > > > > 
> > > > > > > 
> > > > > > > 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.
> > > > > > > 
> > > > > > 
> > > > > > Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
> > > > > > so much... it's really just a few register writes and nothing else, so I really
> > > > > > don't see where the problem is, here.
> > > > > > 
> > > > > > Another issue is that this driver may be largely incomplete, so perhaps I can't
> > > > > > really see the complications you're talking about? Is this the case?
> > > > > > 
> > > > > > Regarding keeping the FHCTL code in separated files, that's fine, but I would still
> > > > > > integrate it tightly in clk-pll and its registration flow, because - yes, this is
> > > > > > for sure not mandatory, but the main parameters are constant, they never change for
> > > > > > a specific PLL, as they're register offsets, bits and masks (which, again, will
> > > > > > never change as long as we're using the same SoC).
> > > > 
> > > > The driver may need to supoport microP by future HW design, standalone file clk-
> > > > fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
> > > > and those different init-flow also need to run some communication API with microP.
> > > > Those communication APIs are not suitable to merge into clk-pll.
> > > > 
> > > 
> > > Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
> > > the microP in the SoC-specific clock drivers, I think that's not a problem?
> > > 
> > > clk_mtfuturesoc_someip_probe()
> > > {
> > > 	.... register clocks ....
> > > 
> > > 	freqhopping_microp_init();
> > > 
> > > 	return ret;
> > > }
> > > 
> > > If there's hardware out there that supports such feature and a downstream kernel to
> > > look at, please tell me which one, so that I will be able to check it out and
> > > perhaps understand how this flow works.
> > > 
> > > P.S.: I guess it's not fhctl-sspm?
> > 
> > You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
> > kernel. Those codes require the PLL hardware specification to determine which PLL
> > group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
> > communicate with the microP.
> > 
> > If we implement these things into clk-pll driver, clk-pll driver not only needs to control
> > PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
> > works that is not belong to PLL operation. That's why we tend to have a standalone driver
> > for FHCTL.
> > 
> 
> Ok having something to analyze made this entire thing a bit more clear in my mind,
> thanks for the pointers.
> 
> Analyzing clk-fhctl-mcupm and clk-fhctl-gpueb makes me see that there's a lot of
> common code between the two: x_hopping_v1(), x_ssc_enable_v1(), x_ssc_disable_v1()
> (where x = {gpueb,mcupm}) are really the same functions, duplicated and renamed
> and nothing else.
> The only difference is the get_xxxx_ipidev(), which is avoidable by assigning
> mboxes = <...something...> in devicetree (gpueb mailbox, or mcupm mailbox).
> 
> Even the `FH_DEVCTL_CMD_ID` enumeration uses the same values!
> 
> To unroll that riddle, I would at that point add a new MediaTek specific clock
> driver (like clk-pll) and call it `clk-ipi.c`, because that's what it does in
> the end: whatever we do, goes through a mailbox instead of a direct MMIO write.
> 
> That clk-fhctl-ipi would contain a probe function that gets the mailbox handle,
> then we would add something like `clk_fhctl_set_rate()` function, export it in
> the `clk-mtk.h` or in a new `clk-fhctl.h` header, then assign the right callback
> in either the SoC's clock driver (by registering a different clock type, which,
> in this case, would be clk-fhctl-ipi instead of clk-pll), or in clk-pll itself...
> 
> In the end, I'm effectively proposing to:
> 
> 1. Merge the direct-MMIO handling of FHCTL in clk-pll;
> 2. Create a new driver (and clock type, eventually) for the IPI handling of FHCTL.

From your idea, I think we can also create a new clock type for fhctl such as clk-pll-fh 
and add a new PLL register function for PLL+FHCTL. Then we can change the registery
interface and won't affect the legacy ICs. Also, if FHCTL has changes, we only need to
modify clk-pll-fh.
I think using a new clock type has extendibility for FHCTL changes and also compatiable
with legacy ICs.

clk-pll.h
	/* Define FHCTL data structure and contains mtk_pll_data. 
	 * We can use mtk_pll_data later. */
	mtk_pll_fh_data {
		struct mtk_pll_data pll_data;
		/* fhctl_data */
		unsigned int fh_id;
		unsigned int ssc_rate;
		...
	}

clk-mt8186-apmixedsys.c
	func clk_mt8186_apmixed_probe()
		/* There are two implementations.
		 * If ICs need FHCTL such as MT8186, use mtk_clk_register_pllfhs()
		 * For those legacy ICs which don't need FHCTL, still use 
		 * mtk_clk_register_plls().
		 */
		/* 1. Need FHCTL. Use API from clk-pll-fh.c */
		fhctl_parse_dt()
		mtk_clk_register_pllfhs(plls data, fh-plls data)
		
		/* 2. Legacy ICs. Use API from clk-pll.c */
		mtk_clk_register_plls()

clk-pll.c
	/* No functional changes, so legacy ICs won't be affected. 
	 * Export clk_ops functions to clk-pll-fh.c
	 */ 
	func mtk_clk_register_plls()
		init.ops = &mtk_pll_ops;

clk-pll-fh.c
	/* A clock type of FHCTL PLL. Used to setup HW data and ops.
	 * Most of ops functions inherit from clk-pll.c.
	 * If PLL not support or not enable FHCTL, fallback to use &mtk_pll_ops.
	 */
	func mtk_clk_register_pllfhs(plls data, fh-plls data)
		fhctl_match_pll_data()  /* match mtk_pll_data and mtk_pll_fh_data */
		fhctl_hw_init()
		if (fhctl_is_supported_and_enabled(pll))
			init.ops = &mtk_pll_fhctl_ops;
		else
			init.ops = &mtk_pll_ops;
	
		if (ssc_is_enable(pll))
			fhctl_ssc_enable(pll)

clk-fhctl.c
	/* APIs of FHCTL HW operations */
	func fhctl_hw_init()
	func fhctl_hopping()
	func fhctl_ssc_enable()


> 
> Regards,
> Angelo
AngeloGioacchino Del Regno July 28, 2022, 8:21 a.m. UTC | #11
Il 28/07/22 06:37, Edward-JW Yang ha scritto:
> Hi AngeloGioacchino,
> 
> Thanks for the advices.
> 
> On Thu, 2022-07-21 at 17:43 +0800, AngeloGioacchino Del Regno wrote:
>> Il 20/07/22 15:51, Edward-JW Yang ha scritto:
>>> Hi AngeloGioacchino,
>>>
>>> Thanks for all the advices and examples.
>>>
>>> On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
>>>> Il 06/07/22 15:07, Edward-JW Yang ha scritto:
>>>>> On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
>>>>>> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
>>>>>> <angelogioacchino.delregno@collabora.com> wrote:
>>>>>>>
>>>>>>> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
>>>>>>>> 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@mediatek.com>
>>>>>>>>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
>>>>>>>>>
>>>>>>>>> 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.
>>>>>>>
>>>>>>> I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
>>>>>>> In any case, this not being a *mandatory* component doesn't mean that when it is
>>>>>>> enabled it's not changing the way we manage the PLLs..........
>>>>>>>
>>>>>>>> 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
>>>>>>>
>>>>>>> I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
>>>>>>> APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
>>>>>>> the set_rate() logic of clk-pll.
>>>>>
>>>>> Do you mean we need to drop the current set_rate() logic (direct register write) and
>>>>> use Frequency Hopping Controller instead?
>>>>>
>>>>
>>>> On PLLs that are supported by the Frequency Hopping controller, yes: we should
>>>> simply use a different .set_rate() callback in clk-pll.c, and we should return
>>>> a failure if the FHCTL fails to set the rate - so we should *not* fall back to
>>>> direct register writes, as on some platforms and in some conditions, using
>>>> direct register writes (which means that we skip FHCTL), may lead to unstable
>>>> system.
>>>>
>>>> This means that we need logic such that, in mtk_clk_register_pll(), we end up
>>>> having something like that:
>>>>
>>>> if (fhctl_is_enabled(pll))
>>>> 	init.ops = &mtk_pll_fhctl_ops;
>>>> else
>>>> 	init.ops = &mtk_pll_ops;
>>>>
>>>>> I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
>>>>> choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
>>>>> HW.
>>>>
>>>> Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
>>>> that such PLL can be managed by FHCTL, for example:
>>>>
>>>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>>>
>>>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
>>>>
>>>> becomes
>>>>
>>>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>>>
>>>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
>>>>
>>>> where 'true' means "FHCTL is supported".
>>>
>>> Does it still have an independent FHCTL driver after modifying to this? From your example,
>>> setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
>>> clk-pll and become part of clk-pll driver.
>>>
>>
>> The direct-MMIO part of FHCTL becomes part of the clk-pll driver, yes - but then
>> I also find it unacceptable to embed the IPI communication inside of there, so we
>> can have an "external" helper for that.
> 
> I think clk-pll driver should focus on PLL HW itself. Since PLL can work alone without
> FHCTL, adding FHCTL control into clk-pll may be a little strange. For this PLL+FHCTL
> combination, I want to add a new type of clock driver, like clk-pll-fh. It might be a
> easier way to maintain FHCTL HW related changes and won't affect to clk-pll.
> 

That makes sense, and it's doable as long as we're not duplicating clk-pll's code
to clk-pll-fh and also as long as we're hardcoding the availability of FHCTL in the
SoC-specific clock drivers like I explained in the PLL macro from the previous
example. Let's go!


>>
>>
>>> We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
>>> callback function instead of whole clk_ops. The boot-up sequence is like:
>>>
>>> 1. clk-pll + clk dts
>>> 	probe  -> clk-pll original flow, nothing to change
>>>
>>>           /* clk-pll provide multation API for set_rate */
>>> 	/* mutate necessary set_rate() instead of mutating all ops */
>>> 		def register_fhctl_set_rate(pll_name, callback)
>>> 			ops = find_pll_ops_by_name(pll_name)
>>> 			log("change set_rate to fhctl callback for $pll_name")
>>> 			ops->set_rate = callback
>>>
>>> 2. FHCTL driver + fhctl dts
>>> 	probe
>>> 		options = parsing dts (board specific, hopping disalbe or ssc-rate)
>>> 		init FHCTL HW
>>> 		for PLL in dts
>>> 			if (ssc-rate > 0)
>>> 				enable_ssc(ssc-rate)
>>> 			if (hop-enabled)
>>> 				/* mutate CCF set_rate, FHCTL engaged CCF */
>>> 				register_fhctl_CCF(pll_name, callback)
>>>
>>
>> I really don't like having PLL names in devicetree: they're already defined in
>> clock drivers and they will change on a per-SoC basis - and we do have per-SoC
>> drivers...
>>
>> Whatever goes to devicetree should be something that we need to vary on a
>> per-board/platform(project) basis, so, enablement of FHCTL per-pll (by using
>> handles and numeral bindings as per the example that I previously wrote),
>> enablement of spread spectrum and its rate... and nothing else.
> 
> OK, we will remove PLL names in devicetree.
> 

Great.

>>
>>>>
>>>> Then, we register the PLLs with something like:
>>>>
>>>> mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
>>>>
>>>> ...where fhctl_register_version is used to assign the right fhctl register offsets.
>>>> Also, it's not needed to assign all of the register offsets statically, because
>>>> they can be easily calculated based on the number of supported PLLs, since the
>>>> registers are structured like
>>>>
>>>> [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
>>>> [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
>>>>
>>>> [FHCTL PER-PLL REGISTERS] <--- CFG...MON
>>>> ^^^ where this is repeated X times for X PLLs.
>>>>
>>>> so, keeping the example of MT8186, we can get the per-pll register like:
>>>>
>>>> #define FHCTL_PLL_OFFSET	0x3c
>>>> #define FHCTL_PLL_LEN		0x14
>>>>
>>>> #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
>>>> #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
>>>> #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
>>>>
>>>> we don't need to put all of them in a structure and for each PLL.
>>>
>>> We use structure instead of using macros is because the register offset may have
>>> difference between ICs. If we use macro, we need to maintain different versions of macros.
>>> Using structure to store these register offsets is more flexible.
>>>
>>
>> I understand. What I don't like about your specific approach is the amount of
>> register offsets that we store in that structure, looks like it's a bit too many.
>>
>> I've seen that there's a common pattern at least by checking downstream 5.10 and
>> MT8186/95 layouts, so I still think that using these macros will be beneficial.
>>
>> We can always add parameters to the structure in a later commit: in my opinion,
>> that will help to engineer a better, shorter, cleaner solution for calculating
>> these registers anyway... but I will leave this choice to you, anyway, since you
>> know about way more SoCs than I do.
> 
> OK, we will reduce the structure.
> 

Perfect!

>>
>>>>
>>>>> So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
>>>>> handle this two types of PLL.
>>>>>
>>>>
>>>> As already said, we preventively know which PLLs support FHCTL and which does not,
>>>> so we can use a different .set_rate() callback.
>>>
>>> Ok, we can use a different .set_rate() callback when fhctl driver probing.
>>>
>>>>
>>>>>>>
>>>>>>>> current set_rate method in clk-pll changes PLL register setting directly. Another
>>>>>>>> way uses FHCTL to change PLL rate.
>>>>>>>
>>>>>>> ...and of course, if we change that, we're effectively mutating the functionality
>>>>>>> of the MediaTek clk-pll driver and please understand that seeing a clear mutation
>>>>>>> in that driver is a bit more human-readable.
>>>>>>>
>>>>>>> Besides, this makes me think about one question: is there any instance in which,
>>>>>>> when FHCTL rate setting fails, we fall back to direct register writes?
>>>>>>>
>>>>>>> I don't think that this is feasible because we have a register in FHCTL that
>>>>>>> effectively hands over control to it, so direct register writes should not work
>>>>>>> when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
>>>>>>> sure that my understanding is right.
>>>>>
>>>>> It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
>>>>> control mode will switch back to APMIXEDSYS after frequency hopping completed.
>>>>>
>>>>> There are two cases that we need to fall back to direct register writes:
>>>>>      1. PLL support FHCTL but it doesn't want to use FHCTL.
>>>>>      2. PLL doesn't support FHCTL HW.
>>>>>
>>>>
>>>> For case N.1, if this is board-specific, we have to resort to devicetree properties
>>>> that will enable/disable FHCTL on specific PLLs.
>>>>
>>>> mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
>>>>
>>>> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
>>>>
>>>> These are just examples - I don't currently know if it's a better idea to have an
>>>> allowlist or a blocklist as devicetree properties, as that depends on the expected
>>>> number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
>>>> enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).
>>>
>>> We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
>>> rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.
>>>
>>
>> Right. For that, we could have a default sensible percentage when SSC is enabled
>> but no rate is set in devicetree, or we can perhaps consider SSC enabled when any
>> meaningful SSC rate is set... For example:
>>
>> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
>> mediatek,ssc-percent = <5>, <5>;
>>
>> ... or something like:
>>
>> mediatek,ssc = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
>>
>> ...but I'd like to have some feedback on that from somebody else, as I don't know
>> if that would be acceptable in devicetree, or if there's any cleaner, niftier
>> solution.
> 
> OK, we will use this:
> 
> mediatek,hopping-ssc-percent = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
> 

Looks good.

>>
>>>>
>>>>>>>
>>>>>>>> We will set some PLL's frequency be controlled
>>>>>>>> by clk-pll and some are controlled by FHCTL.
>>>>>>>
>>>>>>> Another question: is this also changing on a per-board basis?
>>>>>>>
>>>>>>> (note: the pll names in the example are random and not specific to anything)
>>>>>>>
>>>>>>> Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
>>>>>>>                      NNAPLL, MFGPLL
>>>>>>>              board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
>>>>>>>
>>>>>>> Granted that the two A, B boards are using the same SoC, can that ever happen?
>>>>>
>>>>> This could happen if A, B boards have different desense issue.
>>>>>
>>>>
>>>> Ok, so it's definitely board specific. Devicetree is the way to go for this.
>>>>
>>>>>>>
>>>>>>>> And use `perms` param to decide
>>>>>>>> whether a PLL is using FHCTL to change its frequency.
>>>>>>>
>>>>>>> The perms param seems to be about:
>>>>>>>      * Enabling debug (but you're not providing any way to actually use debugging
>>>>>>>        features, so what's the point?)
>>>>>
>>>>> Debugging feature is not used yet, we can removed it.
>>>>>
>>>>
>>>> If the debugging features of the FHCTL driver will be like what I can see on
>>>> the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
>>>>
>>>> In that case, please remove the debug.
>>>
>>> Ok, we will remove it.
>>>
>>>>
>>>>>>>      * Handing over PLL control to FHCTL for hopping (can be as well done with
>>>>>>>        simply using a different .set_rate() callback instead of a flag)
>>>>>
>>>>> There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
>>>>> this case.
>>>>>
>>>>
>>>> Use the flag to set the right .set_rate() callback, set at probe time, instead of
>>>> checking that flag at every set_rate() call.
>>>
>>> We will setup .set_rate() callback when doing fhctl-pll init.
>>>
>>>>
>>>>>>>      * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
>>>>>>>        legit use for flags, but if it's just one flag, you can as well use a
>>>>>>>        bool and manage this with a devicetree param like "enable-ssc")
>>>>>>>
>>>>>>> That said, I think that the current way of enabling the FHCTL is more complicated
>>>>>>> than how it should really be.
>>>>>
>>>>> Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
>>>>> these two are per-board basis.
>>>>>
>>>>> We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
>>>>> FHCTL and not all PLLs have need of using FHCTL-hopping.
>>>>>
>>>>
>>>> Board specific -> devicetree
>>>>
>>>> SoC specific -> hardcode, no devicetree.
>>>>
>>>>>>>
>>>>>>>>
>>>>>>>> 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.
>>>>>>>>
>>>>>>>
>>>>>>> Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
>>>>>>> so much... it's really just a few register writes and nothing else, so I really
>>>>>>> don't see where the problem is, here.
>>>>>>>
>>>>>>> Another issue is that this driver may be largely incomplete, so perhaps I can't
>>>>>>> really see the complications you're talking about? Is this the case?
>>>>>>>
>>>>>>> Regarding keeping the FHCTL code in separated files, that's fine, but I would still
>>>>>>> integrate it tightly in clk-pll and its registration flow, because - yes, this is
>>>>>>> for sure not mandatory, but the main parameters are constant, they never change for
>>>>>>> a specific PLL, as they're register offsets, bits and masks (which, again, will
>>>>>>> never change as long as we're using the same SoC).
>>>>>
>>>>> The driver may need to supoport microP by future HW design, standalone file clk-
>>>>> fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
>>>>> and those different init-flow also need to run some communication API with microP.
>>>>> Those communication APIs are not suitable to merge into clk-pll.
>>>>>
>>>>
>>>> Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
>>>> the microP in the SoC-specific clock drivers, I think that's not a problem?
>>>>
>>>> clk_mtfuturesoc_someip_probe()
>>>> {
>>>> 	.... register clocks ....
>>>>
>>>> 	freqhopping_microp_init();
>>>>
>>>> 	return ret;
>>>> }
>>>>
>>>> If there's hardware out there that supports such feature and a downstream kernel to
>>>> look at, please tell me which one, so that I will be able to check it out and
>>>> perhaps understand how this flow works.
>>>>
>>>> P.S.: I guess it's not fhctl-sspm?
>>>
>>> You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
>>> kernel. Those codes require the PLL hardware specification to determine which PLL
>>> group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
>>> communicate with the microP.
>>>
>>> If we implement these things into clk-pll driver, clk-pll driver not only needs to control
>>> PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
>>> works that is not belong to PLL operation. That's why we tend to have a standalone driver
>>> for FHCTL.
>>>
>>
>> Ok having something to analyze made this entire thing a bit more clear in my mind,
>> thanks for the pointers.
>>
>> Analyzing clk-fhctl-mcupm and clk-fhctl-gpueb makes me see that there's a lot of
>> common code between the two: x_hopping_v1(), x_ssc_enable_v1(), x_ssc_disable_v1()
>> (where x = {gpueb,mcupm}) are really the same functions, duplicated and renamed
>> and nothing else.
>> The only difference is the get_xxxx_ipidev(), which is avoidable by assigning
>> mboxes = <...something...> in devicetree (gpueb mailbox, or mcupm mailbox).
>>
>> Even the `FH_DEVCTL_CMD_ID` enumeration uses the same values!
>>
>> To unroll that riddle, I would at that point add a new MediaTek specific clock
>> driver (like clk-pll) and call it `clk-ipi.c`, because that's what it does in
>> the end: whatever we do, goes through a mailbox instead of a direct MMIO write.
>>
>> That clk-fhctl-ipi would contain a probe function that gets the mailbox handle,
>> then we would add something like `clk_fhctl_set_rate()` function, export it in
>> the `clk-mtk.h` or in a new `clk-fhctl.h` header, then assign the right callback
>> in either the SoC's clock driver (by registering a different clock type, which,
>> in this case, would be clk-fhctl-ipi instead of clk-pll), or in clk-pll itself...
>>
>> In the end, I'm effectively proposing to:
>>
>> 1. Merge the direct-MMIO handling of FHCTL in clk-pll;
>> 2. Create a new driver (and clock type, eventually) for the IPI handling of FHCTL.
> 
>  From your idea, I think we can also create a new clock type for fhctl such as clk-pll-fh
> and add a new PLL register function for PLL+FHCTL. Then we can change the registery
> interface and won't affect the legacy ICs. Also, if FHCTL has changes, we only need to
> modify clk-pll-fh.
> I think using a new clock type has extendibility for FHCTL changes and also compatiable
> with legacy ICs.
> 
> clk-pll.h
> 	/* Define FHCTL data structure and contains mtk_pll_data.
> 	 * We can use mtk_pll_data later. */
> 	mtk_pll_fh_data {
> 		struct mtk_pll_data pll_data;
> 		/* fhctl_data */
> 		unsigned int fh_id;
> 		unsigned int ssc_rate;
> 		...
> 	}
> 
> clk-mt8186-apmixedsys.c
> 	func clk_mt8186_apmixed_probe()
> 		/* There are two implementations.
> 		 * If ICs need FHCTL such as MT8186, use mtk_clk_register_pllfhs()
> 		 * For those legacy ICs which don't need FHCTL, still use
> 		 * mtk_clk_register_plls().
> 		 */
> 		/* 1. Need FHCTL. Use API from clk-pll-fh.c */
> 		fhctl_parse_dt()
> 		mtk_clk_register_pllfhs(plls data, fh-plls data)
> 		
> 		/* 2. Legacy ICs. Use API from clk-pll.c */
> 		mtk_clk_register_plls()

I'm not sure if we're saying the very same thing here, but for the sake of being
clear and avoiding any misunderstanding, here's my description.


We should call both functions: register_pllfhs() for the PLLs that have support for
freqhopping, register_plls() for the ones that *do not support* freqhopping.

Example for PLL_A, PLL_B, PLL_C, PLL_D:

Freqhopping supported (enabled or not): PLL_A, PLL_B
Freqhopping NOT supported at all: PLL_C, PLL_D

fhplls_data[] = { PLL_A, PLL_B };
plls_data[] = { PLL_C, PLL_D };

func mtk_clk_register_pllfhs(fhdata)
	walk through fhplls_data only, other plls are not passed to this function

func clk_mt8186_apmixed_probe()
	/* Some PLLs must be controlled directly via MMIO, while others
	 * support Frequency Hopping through FHCTL.
	 * Where FHCTL is supported, register clock with register_pllfhs.
	 * PLLs that are not supported by FHCTL: register with register_plls.
	 */

	/* Register FHCTL PLLs */
	fhctl_parse_dt()
	mtk_clk_register_pllfhs(array of plls supporting pllfh)

	/* Register the PLLs that do not support FHCTL at all */
	mtk_clk_register_plls(all the other PLLs that cannot be FHCTL-controlled)

> 
> clk-pll.c
> 	/* No functional changes, so legacy ICs won't be affected.
> 	 * Export clk_ops functions to clk-pll-fh.c
> 	 */
> 	func mtk_clk_register_plls()
> 		init.ops = &mtk_pll_ops;
> 
> clk-pll-fh.c
> 	/* A clock type of FHCTL PLL. Used to setup HW data and ops.
> 	 * Most of ops functions inherit from clk-pll.c.
> 	 * If PLL not support or not enable FHCTL, fallback to use &mtk_pll_ops.
> 	 */
> 	func mtk_clk_register_pllfhs(plls data, fh-plls data)
> 		fhctl_match_pll_data()  /* match mtk_pll_data and mtk_pll_fh_data */
> 		fhctl_hw_init()
> 		if (fhctl_is_supported_and_enabled(pll))

Overall, this seems to look good, minor one nit: if FHCTL is *not supported* on
a PLL, we should *not* even call mtk_clk_register_pllfhs on that PLL, so your
pseudocode would be just:

		if (fhctl_is_enabled(pll))

> 			init.ops = &mtk_pll_fhctl_ops;
> 		else
> 			init.ops = &mtk_pll_ops;
> 	
> 		if (ssc_is_enable(pll))
> 			fhctl_ssc_enable(pll)
> 
> clk-fhctl.c
> 	/* APIs of FHCTL HW operations */
> 	func fhctl_hw_init()
> 	func fhctl_hopping()
> 	func fhctl_ssc_enable()
> 
> 

So it seems that we've reached an agreement here, this was a nice planning
discussion; we should now have a nice and solid base to work on, which is
great.

Cheers,
Angelo
Edward-JW Yang July 28, 2022, 3:30 p.m. UTC | #12
Hi AngeloGioacchino,

Thanks for the discussion.

On Thu, 2022-07-28 at 16:21 +0800, AngeloGioacchino Del Regno wrote:
> Il 28/07/22 06:37, Edward-JW Yang ha scritto:
> > Hi AngeloGioacchino,
> > 
> > Thanks for the advices.
> > 
> > On Thu, 2022-07-21 at 17:43 +0800, AngeloGioacchino Del Regno wrote:
> > > Il 20/07/22 15:51, Edward-JW Yang ha scritto:
> > > > Hi AngeloGioacchino,
> > > > 
> > > > Thanks for all the advices and examples.
> > > > 
> > > > On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
> > > > > Il 06/07/22 15:07, Edward-JW Yang ha scritto:
> > > > > > On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
> > > > > > > On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
> > > > > > > <angelogioacchino.delregno@collabora.com> wrote:
> > > > > > > > 
> > > > > > > > Il 24/06/22 09:12, Edward-JW Yang ha scritto:
> > > > > > > > > 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@mediatek.com>
> > > > > > > > > > > Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
> > > > > > > > > > 
> > > > > > > > > > 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.
> > > > > > > > 
> > > > > > > > I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
> > > > > > > > In any case, this not being a *mandatory* component doesn't mean that when it is
> > > > > > > > enabled it's not changing the way we manage the PLLs..........
> > > > > > > > 
> > > > > > > > > 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
> > > > > > > > 
> > > > > > > > I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
> > > > > > > > APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
> > > > > > > > the set_rate() logic of clk-pll.
> > > > > > 
> > > > > > Do you mean we need to drop the current set_rate() logic (direct register write) and
> > > > > > use Frequency Hopping Controller instead?
> > > > > > 
> > > > > 
> > > > > On PLLs that are supported by the Frequency Hopping controller, yes: we should
> > > > > simply use a different .set_rate() callback in clk-pll.c, and we should return
> > > > > a failure if the FHCTL fails to set the rate - so we should *not* fall back to
> > > > > direct register writes, as on some platforms and in some conditions, using
> > > > > direct register writes (which means that we skip FHCTL), may lead to unstable
> > > > > system.
> > > > > 
> > > > > This means that we need logic such that, in mtk_clk_register_pll(), we end up
> > > > > having something like that:
> > > > > 
> > > > > if (fhctl_is_enabled(pll))
> > > > > 	init.ops = &mtk_pll_fhctl_ops;
> > > > > else
> > > > > 	init.ops = &mtk_pll_ops;
> > > > > 
> > > > > > I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
> > > > > > choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
> > > > > > HW.
> > > > > 
> > > > > Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
> > > > > that such PLL can be managed by FHCTL, for example:
> > > > > 
> > > > > 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> > > > > 
> > > > > 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
> > > > > 
> > > > > becomes
> > > > > 
> > > > > 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
> > > > > 
> > > > > 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
> > > > > 
> > > > > where 'true' means "FHCTL is supported".
> > > > 
> > > > Does it still have an independent FHCTL driver after modifying to this? From your example,
> > > > setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
> > > > clk-pll and become part of clk-pll driver.
> > > > 
> > > 
> > > The direct-MMIO part of FHCTL becomes part of the clk-pll driver, yes - but then
> > > I also find it unacceptable to embed the IPI communication inside of there, so we
> > > can have an "external" helper for that.
> > 
> > I think clk-pll driver should focus on PLL HW itself. Since PLL can work alone without
> > FHCTL, adding FHCTL control into clk-pll may be a little strange. For this PLL+FHCTL
> > combination, I want to add a new type of clock driver, like clk-pll-fh. It might be a
> > easier way to maintain FHCTL HW related changes and won't affect to clk-pll.
> > 
> 
> That makes sense, and it's doable as long as we're not duplicating clk-pll's code
> to clk-pll-fh and also as long as we're hardcoding the availability of FHCTL in the
> SoC-specific clock drivers like I explained in the PLL macro from the previous
> example. Let's go!
> 
> 
> > > 
> > > 
> > > > We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
> > > > callback function instead of whole clk_ops. The boot-up sequence is like:
> > > > 
> > > > 1. clk-pll + clk dts
> > > > 	probe  -> clk-pll original flow, nothing to change
> > > > 
> > > >           /* clk-pll provide multation API for set_rate */
> > > > 	/* mutate necessary set_rate() instead of mutating all ops */
> > > > 		def register_fhctl_set_rate(pll_name, callback)
> > > > 			ops = find_pll_ops_by_name(pll_name)
> > > > 			log("change set_rate to fhctl callback for $pll_name")
> > > > 			ops->set_rate = callback
> > > > 
> > > > 2. FHCTL driver + fhctl dts
> > > > 	probe
> > > > 		options = parsing dts (board specific, hopping disalbe or ssc-rate)
> > > > 		init FHCTL HW
> > > > 		for PLL in dts
> > > > 			if (ssc-rate > 0)
> > > > 				enable_ssc(ssc-rate)
> > > > 			if (hop-enabled)
> > > > 				/* mutate CCF set_rate, FHCTL engaged CCF */
> > > > 				register_fhctl_CCF(pll_name, callback)
> > > > 
> > > 
> > > I really don't like having PLL names in devicetree: they're already defined in
> > > clock drivers and they will change on a per-SoC basis - and we do have per-SoC
> > > drivers...
> > > 
> > > Whatever goes to devicetree should be something that we need to vary on a
> > > per-board/platform(project) basis, so, enablement of FHCTL per-pll (by using
> > > handles and numeral bindings as per the example that I previously wrote),
> > > enablement of spread spectrum and its rate... and nothing else.
> > 
> > OK, we will remove PLL names in devicetree.
> > 
> 
> Great.
> 
> > > 
> > > > > 
> > > > > Then, we register the PLLs with something like:
> > > > > 
> > > > > mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
> > > > > 
> > > > > ...where fhctl_register_version is used to assign the right fhctl register offsets.
> > > > > Also, it's not needed to assign all of the register offsets statically, because
> > > > > they can be easily calculated based on the number of supported PLLs, since the
> > > > > registers are structured like
> > > > > 
> > > > > [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
> > > > > [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
> > > > > 
> > > > > [FHCTL PER-PLL REGISTERS] <--- CFG...MON
> > > > > ^^^ where this is repeated X times for X PLLs.
> > > > > 
> > > > > so, keeping the example of MT8186, we can get the per-pll register like:
> > > > > 
> > > > > #define FHCTL_PLL_OFFSET	0x3c
> > > > > #define FHCTL_PLL_LEN		0x14
> > > > > 
> > > > > #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
> > > > > #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
> > > > > #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
> > > > > 
> > > > > we don't need to put all of them in a structure and for each PLL.
> > > > 
> > > > We use structure instead of using macros is because the register offset may have
> > > > difference between ICs. If we use macro, we need to maintain different versions of macros.
> > > > Using structure to store these register offsets is more flexible.
> > > > 
> > > 
> > > I understand. What I don't like about your specific approach is the amount of
> > > register offsets that we store in that structure, looks like it's a bit too many.
> > > 
> > > I've seen that there's a common pattern at least by checking downstream 5.10 and
> > > MT8186/95 layouts, so I still think that using these macros will be beneficial.
> > > 
> > > We can always add parameters to the structure in a later commit: in my opinion,
> > > that will help to engineer a better, shorter, cleaner solution for calculating
> > > these registers anyway... but I will leave this choice to you, anyway, since you
> > > know about way more SoCs than I do.
> > 
> > OK, we will reduce the structure.
> > 
> 
> Perfect!
> 
> > > 
> > > > > 
> > > > > > So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
> > > > > > handle this two types of PLL.
> > > > > > 
> > > > > 
> > > > > As already said, we preventively know which PLLs support FHCTL and which does not,
> > > > > so we can use a different .set_rate() callback.
> > > > 
> > > > Ok, we can use a different .set_rate() callback when fhctl driver probing.
> > > > 
> > > > > 
> > > > > > > > 
> > > > > > > > > current set_rate method in clk-pll changes PLL register setting directly. Another
> > > > > > > > > way uses FHCTL to change PLL rate.
> > > > > > > > 
> > > > > > > > ...and of course, if we change that, we're effectively mutating the functionality
> > > > > > > > of the MediaTek clk-pll driver and please understand that seeing a clear mutation
> > > > > > > > in that driver is a bit more human-readable.
> > > > > > > > 
> > > > > > > > Besides, this makes me think about one question: is there any instance in which,
> > > > > > > > when FHCTL rate setting fails, we fall back to direct register writes?
> > > > > > > > 
> > > > > > > > I don't think that this is feasible because we have a register in FHCTL that
> > > > > > > > effectively hands over control to it, so direct register writes should not work
> > > > > > > > when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
> > > > > > > > sure that my understanding is right.
> > > > > > 
> > > > > > It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
> > > > > > control mode will switch back to APMIXEDSYS after frequency hopping completed.
> > > > > > 
> > > > > > There are two cases that we need to fall back to direct register writes:
> > > > > >      1. PLL support FHCTL but it doesn't want to use FHCTL.
> > > > > >      2. PLL doesn't support FHCTL HW.
> > > > > > 
> > > > > 
> > > > > For case N.1, if this is board-specific, we have to resort to devicetree properties
> > > > > that will enable/disable FHCTL on specific PLLs.
> > > > > 
> > > > > mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
> > > > > 
> > > > > mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
> > > > > 
> > > > > These are just examples - I don't currently know if it's a better idea to have an
> > > > > allowlist or a blocklist as devicetree properties, as that depends on the expected
> > > > > number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
> > > > > enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).
> > > > 
> > > > We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
> > > > rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.
> > > > 
> > > 
> > > Right. For that, we could have a default sensible percentage when SSC is enabled
> > > but no rate is set in devicetree, or we can perhaps consider SSC enabled when any
> > > meaningful SSC rate is set... For example:
> > > 
> > > mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
> > > mediatek,ssc-percent = <5>, <5>;
> > > 
> > > ... or something like:
> > > 
> > > mediatek,ssc = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
> > > 
> > > ...but I'd like to have some feedback on that from somebody else, as I don't know
> > > if that would be acceptable in devicetree, or if there's any cleaner, niftier
> > > solution.
> > 
> > OK, we will use this:
> > 
> > mediatek,hopping-ssc-percent = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
> > 
> 
> Looks good.
> 
> > > 
> > > > > 
> > > > > > > > 
> > > > > > > > > We will set some PLL's frequency be controlled
> > > > > > > > > by clk-pll and some are controlled by FHCTL.
> > > > > > > > 
> > > > > > > > Another question: is this also changing on a per-board basis?
> > > > > > > > 
> > > > > > > > (note: the pll names in the example are random and not specific to anything)
> > > > > > > > 
> > > > > > > > Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
> > > > > > > >                      NNAPLL, MFGPLL
> > > > > > > >              board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
> > > > > > > > 
> > > > > > > > Granted that the two A, B boards are using the same SoC, can that ever happen?
> > > > > > 
> > > > > > This could happen if A, B boards have different desense issue.
> > > > > > 
> > > > > 
> > > > > Ok, so it's definitely board specific. Devicetree is the way to go for this.
> > > > > 
> > > > > > > > 
> > > > > > > > > And use `perms` param to decide
> > > > > > > > > whether a PLL is using FHCTL to change its frequency.
> > > > > > > > 
> > > > > > > > The perms param seems to be about:
> > > > > > > >      * Enabling debug (but you're not providing any way to actually use debugging
> > > > > > > >        features, so what's the point?)
> > > > > > 
> > > > > > Debugging feature is not used yet, we can removed it.
> > > > > > 
> > > > > 
> > > > > If the debugging features of the FHCTL driver will be like what I can see on
> > > > > the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
> > > > > 
> > > > > In that case, please remove the debug.
> > > > 
> > > > Ok, we will remove it.
> > > > 
> > > > > 
> > > > > > > >      * Handing over PLL control to FHCTL for hopping (can be as well done with
> > > > > > > >        simply using a different .set_rate() callback instead of a flag)
> > > > > > 
> > > > > > There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
> > > > > > this case.
> > > > > > 
> > > > > 
> > > > > Use the flag to set the right .set_rate() callback, set at probe time, instead of
> > > > > checking that flag at every set_rate() call.
> > > > 
> > > > We will setup .set_rate() callback when doing fhctl-pll init.
> > > > 
> > > > > 
> > > > > > > >      * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
> > > > > > > >        legit use for flags, but if it's just one flag, you can as well use a
> > > > > > > >        bool and manage this with a devicetree param like "enable-ssc")
> > > > > > > > 
> > > > > > > > That said, I think that the current way of enabling the FHCTL is more complicated
> > > > > > > > than how it should really be.
> > > > > > 
> > > > > > Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
> > > > > > these two are per-board basis.
> > > > > > 
> > > > > > We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
> > > > > > FHCTL and not all PLLs have need of using FHCTL-hopping.
> > > > > > 
> > > > > 
> > > > > Board specific -> devicetree
> > > > > 
> > > > > SoC specific -> hardcode, no devicetree.
> > > > > 
> > > > > > > > 
> > > > > > > > > 
> > > > > > > > > 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.
> > > > > > > > > 
> > > > > > > > 
> > > > > > > > Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
> > > > > > > > so much... it's really just a few register writes and nothing else, so I really
> > > > > > > > don't see where the problem is, here.
> > > > > > > > 
> > > > > > > > Another issue is that this driver may be largely incomplete, so perhaps I can't
> > > > > > > > really see the complications you're talking about? Is this the case?
> > > > > > > > 
> > > > > > > > Regarding keeping the FHCTL code in separated files, that's fine, but I would still
> > > > > > > > integrate it tightly in clk-pll and its registration flow, because - yes, this is
> > > > > > > > for sure not mandatory, but the main parameters are constant, they never change for
> > > > > > > > a specific PLL, as they're register offsets, bits and masks (which, again, will
> > > > > > > > never change as long as we're using the same SoC).
> > > > > > 
> > > > > > The driver may need to supoport microP by future HW design, standalone file clk-
> > > > > > fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
> > > > > > and those different init-flow also need to run some communication API with microP.
> > > > > > Those communication APIs are not suitable to merge into clk-pll.
> > > > > > 
> > > > > 
> > > > > Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
> > > > > the microP in the SoC-specific clock drivers, I think that's not a problem?
> > > > > 
> > > > > clk_mtfuturesoc_someip_probe()
> > > > > {
> > > > > 	.... register clocks ....
> > > > > 
> > > > > 	freqhopping_microp_init();
> > > > > 
> > > > > 	return ret;
> > > > > }
> > > > > 
> > > > > If there's hardware out there that supports such feature and a downstream kernel to
> > > > > look at, please tell me which one, so that I will be able to check it out and
> > > > > perhaps understand how this flow works.
> > > > > 
> > > > > P.S.: I guess it's not fhctl-sspm?
> > > > 
> > > > You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
> > > > kernel. Those codes require the PLL hardware specification to determine which PLL
> > > > group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
> > > > communicate with the microP.
> > > > 
> > > > If we implement these things into clk-pll driver, clk-pll driver not only needs to control
> > > > PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
> > > > works that is not belong to PLL operation. That's why we tend to have a standalone driver
> > > > for FHCTL.
> > > > 
> > > 
> > > Ok having something to analyze made this entire thing a bit more clear in my mind,
> > > thanks for the pointers.
> > > 
> > > Analyzing clk-fhctl-mcupm and clk-fhctl-gpueb makes me see that there's a lot of
> > > common code between the two: x_hopping_v1(), x_ssc_enable_v1(), x_ssc_disable_v1()
> > > (where x = {gpueb,mcupm}) are really the same functions, duplicated and renamed
> > > and nothing else.
> > > The only difference is the get_xxxx_ipidev(), which is avoidable by assigning
> > > mboxes = <...something...> in devicetree (gpueb mailbox, or mcupm mailbox).
> > > 
> > > Even the `FH_DEVCTL_CMD_ID` enumeration uses the same values!
> > > 
> > > To unroll that riddle, I would at that point add a new MediaTek specific clock
> > > driver (like clk-pll) and call it `clk-ipi.c`, because that's what it does in
> > > the end: whatever we do, goes through a mailbox instead of a direct MMIO write.
> > > 
> > > That clk-fhctl-ipi would contain a probe function that gets the mailbox handle,
> > > then we would add something like `clk_fhctl_set_rate()` function, export it in
> > > the `clk-mtk.h` or in a new `clk-fhctl.h` header, then assign the right callback
> > > in either the SoC's clock driver (by registering a different clock type, which,
> > > in this case, would be clk-fhctl-ipi instead of clk-pll), or in clk-pll itself...
> > > 
> > > In the end, I'm effectively proposing to:
> > > 
> > > 1. Merge the direct-MMIO handling of FHCTL in clk-pll;
> > > 2. Create a new driver (and clock type, eventually) for the IPI handling of FHCTL.
> > 
> >  From your idea, I think we can also create a new clock type for fhctl such as clk-pll-fh
> > and add a new PLL register function for PLL+FHCTL. Then we can change the registery
> > interface and won't affect the legacy ICs. Also, if FHCTL has changes, we only need to
> > modify clk-pll-fh.
> > I think using a new clock type has extendibility for FHCTL changes and also compatiable
> > with legacy ICs.
> > 
> > clk-pll.h
> > 	/* Define FHCTL data structure and contains mtk_pll_data.
> > 	 * We can use mtk_pll_data later. */
> > 	mtk_pll_fh_data {
> > 		struct mtk_pll_data pll_data;
> > 		/* fhctl_data */
> > 		unsigned int fh_id;
> > 		unsigned int ssc_rate;
> > 		...
> > 	}
> > 
> > clk-mt8186-apmixedsys.c
> > 	func clk_mt8186_apmixed_probe()
> > 		/* There are two implementations.
> > 		 * If ICs need FHCTL such as MT8186, use mtk_clk_register_pllfhs()
> > 		 * For those legacy ICs which don't need FHCTL, still use
> > 		 * mtk_clk_register_plls().
> > 		 */
> > 		/* 1. Need FHCTL. Use API from clk-pll-fh.c */
> > 		fhctl_parse_dt()
> > 		mtk_clk_register_pllfhs(plls data, fh-plls data)
> > 		
> > 		/* 2. Legacy ICs. Use API from clk-pll.c */
> > 		mtk_clk_register_plls()
> 
> I'm not sure if we're saying the very same thing here, but for the sake of being
> clear and avoiding any misunderstanding, here's my description.
> 
> 
> We should call both functions: register_pllfhs() for the PLLs that have support for
> freqhopping, register_plls() for the ones that *do not support* freqhopping.
> 
> Example for PLL_A, PLL_B, PLL_C, PLL_D:
> 
> Freqhopping supported (enabled or not): PLL_A, PLL_B
> Freqhopping NOT supported at all: PLL_C, PLL_D
> 
> fhplls_data[] = { PLL_A, PLL_B };
> plls_data[] = { PLL_C, PLL_D };
> 
> func mtk_clk_register_pllfhs(fhdata)
> 	walk through fhplls_data only, other plls are not passed to this function
> 
> func clk_mt8186_apmixed_probe()
> 	/* Some PLLs must be controlled directly via MMIO, while others
> 	 * support Frequency Hopping through FHCTL.
> 	 * Where FHCTL is supported, register clock with register_pllfhs.
> 	 * PLLs that are not supported by FHCTL: register with register_plls.
> 	 */
> 
> 	/* Register FHCTL PLLs */
> 	fhctl_parse_dt()
> 	mtk_clk_register_pllfhs(array of plls supporting pllfh)
> 
> 	/* Register the PLLs that do not support FHCTL at all */
> 	mtk_clk_register_plls(all the other PLLs that cannot be FHCTL-controlled)
> 

I have a different opinion here. I think we should just choose one register function when
probe and it depends on platform implementation instead of a PLL is supported FHCTL or
not. We have an option to use clk-pll-fh or clk-pll at the beginning of new IC
development. And, we want it's easy to add FHCTL support on legecy ICs.

The way of how we use the register function is affected by the way we use fhplls_data. If
FHCTL supported or not is used to decide which registery function should we use, it has to
change the exist plls_data. But most of IC's PLL data is already exist. So I want to use a
structure to descript FHCTL HW itself and have a link to existed PLL data.

From your example, it will look like:

plls_data = { PLL_A, PLL_B, PLL_C, PLL_D };  /* Already existed */
fhplls_data = { FH_A,
FH_B };  /* New added for FHCTL data */

func mtk_clk_register_pllfhs(plls_data, fhplls_data)
	fhctl_match_pll_data()
		/* Link fhpll and pll:
		 *   FH_A -> PLL_A
		 *   FH_B -> PLL_B
		 */

For MT8186 and ICs need FHCTL supported:
	func clk_mt8186_apmixed_probe()
		fhctl_parse_dt()
		mtk_clk_register_pllfhs(plls_data, fhplls_data)

For those don't need FHCTL and legacy ICs:
	func clk_mtxxxx_apmixed_probe()
		mtk_clk_register_plls(plls_data)

When one ICs that need to support FHCTL but they didn't implement before, we only need to
change the registery function and add FHCTL data, no need to modify existed plls_data. I
think it can save our development time and maintenance effort.

> > 
> > clk-pll.c
> > 	/* No functional changes, so legacy ICs won't be affected.
> > 	 * Export clk_ops functions to clk-pll-fh.c
> > 	 */
> > 	func mtk_clk_register_plls()
> > 		init.ops = &mtk_pll_ops;
> > 
> > clk-pll-fh.c
> > 	/* A clock type of FHCTL PLL. Used to setup HW data and ops.
> > 	 * Most of ops functions inherit from clk-pll.c.
> > 	 * If PLL not support or not enable FHCTL, fallback to use &mtk_pll_ops.
> > 	 */
> > 	func mtk_clk_register_pllfhs(plls data, fh-plls data)
> > 		fhctl_match_pll_data()  /* match mtk_pll_data and mtk_pll_fh_data */
> > 		fhctl_hw_init()
> > 		if (fhctl_is_supported_and_enabled(pll))
> 
> Overall, this seems to look good, minor one nit: if FHCTL is *not supported* on
> a PLL, we should *not* even call mtk_clk_register_pllfhs on that PLL, so your
> pseudocode would be just:
> 
> 		if (fhctl_is_enabled(pll))

The requirements of clk-pll-fh are:
1. It is compatible with those PLLs that don't support FHCTL HW.
2. It can handle PLLs that supported FHCTL but aren't enabled.
3. It can handle PLLs that supported FHCTL but are enabled.

So, I use fhctl_is_supported_and_enabled(PLL) here. No matter a PLL supports FHCTL or not,
it can work.

> 
> > 			init.ops = &mtk_pll_fhctl_ops;
> > 		else
> > 			init.ops = &mtk_pll_ops;
> > 	
> > 		if (ssc_is_enable(pll))
> > 			fhctl_ssc_enable(pll)
> > 
> > clk-fhctl.c
> > 	/* APIs of FHCTL HW operations */
> > 	func fhctl_hw_init()
> > 	func fhctl_hopping()
> > 	func fhctl_ssc_enable()
> > 
> > 
> 
> So it seems that we've reached an agreement here, this was a nice planning
> discussion; we should now have a nice and solid base to work on, which is
> great.
> 
> Cheers,
> Angelo
AngeloGioacchino Del Regno July 28, 2022, 3:47 p.m. UTC | #13
Il 28/07/22 17:30, Edward-JW Yang ha scritto:
> Hi AngeloGioacchino,
> 
> Thanks for the discussion.
> 
> On Thu, 2022-07-28 at 16:21 +0800, AngeloGioacchino Del Regno wrote:
>> Il 28/07/22 06:37, Edward-JW Yang ha scritto:
>>> Hi AngeloGioacchino,
>>>
>>> Thanks for the advices.
>>>
>>> On Thu, 2022-07-21 at 17:43 +0800, AngeloGioacchino Del Regno wrote:
>>>> Il 20/07/22 15:51, Edward-JW Yang ha scritto:
>>>>> Hi AngeloGioacchino,
>>>>>
>>>>> Thanks for all the advices and examples.
>>>>>
>>>>> On Thu, 2022-07-14 at 19:04 +0800, AngeloGioacchino Del Regno wrote:
>>>>>> Il 06/07/22 15:07, Edward-JW Yang ha scritto:
>>>>>>> On Wed, 2022-06-29 at 16:54 +0800, Chen-Yu Tsai wrote:
>>>>>>>> On Tue, Jun 28, 2022 at 6:09 PM AngeloGioacchino Del Regno
>>>>>>>> <angelogioacchino.delregno@collabora.com> wrote:
>>>>>>>>>
>>>>>>>>> Il 24/06/22 09:12, Edward-JW Yang ha scritto:
>>>>>>>>>> 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@mediatek.com>
>>>>>>>>>>>> Signed-off-by: Johnson Wang <johnson.wang@mediatek.com>
>>>>>>>>>>>
>>>>>>>>>>> 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.
>>>>>>>>>
>>>>>>>>> I know what FHCTL is, but thank you anyway for the explanation, that's appreciated.
>>>>>>>>> In any case, this not being a *mandatory* component doesn't mean that when it is
>>>>>>>>> enabled it's not changing the way we manage the PLLs..........
>>>>>>>>>
>>>>>>>>>> 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
>>>>>>>>>
>>>>>>>>> I disagree: when we want to use FHCTL, we effectively hand-over PLL control from
>>>>>>>>> APMIXEDSYS to the Frequency Hopping controller - and we're effectively replacing
>>>>>>>>> the set_rate() logic of clk-pll.
>>>>>>>
>>>>>>> Do you mean we need to drop the current set_rate() logic (direct register write) and
>>>>>>> use Frequency Hopping Controller instead?
>>>>>>>
>>>>>>
>>>>>> On PLLs that are supported by the Frequency Hopping controller, yes: we should
>>>>>> simply use a different .set_rate() callback in clk-pll.c, and we should return
>>>>>> a failure if the FHCTL fails to set the rate - so we should *not* fall back to
>>>>>> direct register writes, as on some platforms and in some conditions, using
>>>>>> direct register writes (which means that we skip FHCTL), may lead to unstable
>>>>>> system.
>>>>>>
>>>>>> This means that we need logic such that, in mtk_clk_register_pll(), we end up
>>>>>> having something like that:
>>>>>>
>>>>>> if (fhctl_is_enabled(pll))
>>>>>> 	init.ops = &mtk_pll_fhctl_ops;
>>>>>> else
>>>>>> 	init.ops = &mtk_pll_ops;
>>>>>>
>>>>>>> I need to mention that not all PLL support FHCTL, only those PLLs with FHCTL HW can
>>>>>>> choose to use FHCTL. Take 8186 for example, there are three PLLs don't support FHCTL
>>>>>>> HW.
>>>>>>
>>>>>> Where we declare the PLLs, for example, in clk-mt8186-apmixedsys.c, we can declare
>>>>>> that such PLL can be managed by FHCTL, for example:
>>>>>>
>>>>>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>>>>>
>>>>>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208),
>>>>>>
>>>>>> becomes
>>>>>>
>>>>>> 	PLL(CLK_APMIXED_ARMPLL_LL, "armpll_ll", 0x0204, 0x0210, 0,
>>>>>>
>>>>>> 	    PLL_AO, 0, 22, 0x0208, 24, 0, 0, 0, 0x0208, true);
>>>>>>
>>>>>> where 'true' means "FHCTL is supported".
>>>>>
>>>>> Does it still have an independent FHCTL driver after modifying to this? From your example,
>>>>> setup a clk_ops and add FHCTL properities into PLL(), seems FHCTL driver is merged into
>>>>> clk-pll and become part of clk-pll driver.
>>>>>
>>>>
>>>> The direct-MMIO part of FHCTL becomes part of the clk-pll driver, yes - but then
>>>> I also find it unacceptable to embed the IPI communication inside of there, so we
>>>> can have an "external" helper for that.
>>>
>>> I think clk-pll driver should focus on PLL HW itself. Since PLL can work alone without
>>> FHCTL, adding FHCTL control into clk-pll may be a little strange. For this PLL+FHCTL
>>> combination, I want to add a new type of clock driver, like clk-pll-fh. It might be a
>>> easier way to maintain FHCTL HW related changes and won't affect to clk-pll.
>>>
>>
>> That makes sense, and it's doable as long as we're not duplicating clk-pll's code
>> to clk-pll-fh and also as long as we're hardcoding the availability of FHCTL in the
>> SoC-specific clock drivers like I explained in the PLL macro from the previous
>> example. Let's go!
>>
>>
>>>>
>>>>
>>>>> We tend to have an indepentent driver and dts for FHCTL, and mutate only .set_rate()
>>>>> callback function instead of whole clk_ops. The boot-up sequence is like:
>>>>>
>>>>> 1. clk-pll + clk dts
>>>>> 	probe  -> clk-pll original flow, nothing to change
>>>>>
>>>>>            /* clk-pll provide multation API for set_rate */
>>>>> 	/* mutate necessary set_rate() instead of mutating all ops */
>>>>> 		def register_fhctl_set_rate(pll_name, callback)
>>>>> 			ops = find_pll_ops_by_name(pll_name)
>>>>> 			log("change set_rate to fhctl callback for $pll_name")
>>>>> 			ops->set_rate = callback
>>>>>
>>>>> 2. FHCTL driver + fhctl dts
>>>>> 	probe
>>>>> 		options = parsing dts (board specific, hopping disalbe or ssc-rate)
>>>>> 		init FHCTL HW
>>>>> 		for PLL in dts
>>>>> 			if (ssc-rate > 0)
>>>>> 				enable_ssc(ssc-rate)
>>>>> 			if (hop-enabled)
>>>>> 				/* mutate CCF set_rate, FHCTL engaged CCF */
>>>>> 				register_fhctl_CCF(pll_name, callback)
>>>>>
>>>>
>>>> I really don't like having PLL names in devicetree: they're already defined in
>>>> clock drivers and they will change on a per-SoC basis - and we do have per-SoC
>>>> drivers...
>>>>
>>>> Whatever goes to devicetree should be something that we need to vary on a
>>>> per-board/platform(project) basis, so, enablement of FHCTL per-pll (by using
>>>> handles and numeral bindings as per the example that I previously wrote),
>>>> enablement of spread spectrum and its rate... and nothing else.
>>>
>>> OK, we will remove PLL names in devicetree.
>>>
>>
>> Great.
>>
>>>>
>>>>>>
>>>>>> Then, we register the PLLs with something like:
>>>>>>
>>>>>> mtk_clk_register_plls(node, plls, num_plls, clk_data, fhctl_register_version);
>>>>>>
>>>>>> ...where fhctl_register_version is used to assign the right fhctl register offsets.
>>>>>> Also, it's not needed to assign all of the register offsets statically, because
>>>>>> they can be easily calculated based on the number of supported PLLs, since the
>>>>>> registers are structured like
>>>>>>
>>>>>> [FHCTL GLOBAL REGISTERS] <--- hp_en...slope1
>>>>>> [FHCTL SSC GLOBAL REGISTERS] <--- DSSC_CFG, DSSC0...x_CON
>>>>>>
>>>>>> [FHCTL PER-PLL REGISTERS] <--- CFG...MON
>>>>>> ^^^ where this is repeated X times for X PLLs.
>>>>>>
>>>>>> so, keeping the example of MT8186, we can get the per-pll register like:
>>>>>>
>>>>>> #define FHCTL_PLL_OFFSET	0x3c
>>>>>> #define FHCTL_PLL_LEN		0x14
>>>>>>
>>>>>> #define FHCTLx_CFG(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN))
>>>>>> #define FHCTLx_UPDNLMT(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x4)
>>>>>> #define FHCTLx_DDS(pll_id)	(FHCTL_PLL_OFFSET + (pll_id * FHCTL_PLL_LEN) + 0x8)
>>>>>>
>>>>>> we don't need to put all of them in a structure and for each PLL.
>>>>>
>>>>> We use structure instead of using macros is because the register offset may have
>>>>> difference between ICs. If we use macro, we need to maintain different versions of macros.
>>>>> Using structure to store these register offsets is more flexible.
>>>>>
>>>>
>>>> I understand. What I don't like about your specific approach is the amount of
>>>> register offsets that we store in that structure, looks like it's a bit too many.
>>>>
>>>> I've seen that there's a common pattern at least by checking downstream 5.10 and
>>>> MT8186/95 layouts, so I still think that using these macros will be beneficial.
>>>>
>>>> We can always add parameters to the structure in a later commit: in my opinion,
>>>> that will help to engineer a better, shorter, cleaner solution for calculating
>>>> these registers anyway... but I will leave this choice to you, anyway, since you
>>>> know about way more SoCs than I do.
>>>
>>> OK, we will reduce the structure.
>>>
>>
>> Perfect!
>>
>>>>
>>>>>>
>>>>>>> So, we need both APMIXEDSYS and Frequency Hopping Controller in set_rate() logic to
>>>>>>> handle this two types of PLL.
>>>>>>>
>>>>>>
>>>>>> As already said, we preventively know which PLLs support FHCTL and which does not,
>>>>>> so we can use a different .set_rate() callback.
>>>>>
>>>>> Ok, we can use a different .set_rate() callback when fhctl driver probing.
>>>>>
>>>>>>
>>>>>>>>>
>>>>>>>>>> current set_rate method in clk-pll changes PLL register setting directly. Another
>>>>>>>>>> way uses FHCTL to change PLL rate.
>>>>>>>>>
>>>>>>>>> ...and of course, if we change that, we're effectively mutating the functionality
>>>>>>>>> of the MediaTek clk-pll driver and please understand that seeing a clear mutation
>>>>>>>>> in that driver is a bit more human-readable.
>>>>>>>>>
>>>>>>>>> Besides, this makes me think about one question: is there any instance in which,
>>>>>>>>> when FHCTL rate setting fails, we fall back to direct register writes?
>>>>>>>>>
>>>>>>>>> I don't think that this is feasible because we have a register in FHCTL that
>>>>>>>>> effectively hands over control to it, so direct register writes should not work
>>>>>>>>> when the PLL is not under APMIXEDSYS control, but I'm asking just to be extremely
>>>>>>>>> sure that my understanding is right.
>>>>>>>
>>>>>>> It won't fall back to direct register writes when FHCTL rate setting fails. But, PLL
>>>>>>> control mode will switch back to APMIXEDSYS after frequency hopping completed.
>>>>>>>
>>>>>>> There are two cases that we need to fall back to direct register writes:
>>>>>>>       1. PLL support FHCTL but it doesn't want to use FHCTL.
>>>>>>>       2. PLL doesn't support FHCTL HW.
>>>>>>>
>>>>>>
>>>>>> For case N.1, if this is board-specific, we have to resort to devicetree properties
>>>>>> that will enable/disable FHCTL on specific PLLs.
>>>>>>
>>>>>> mediatek,fhctl-disable = <CLK_APMIXED_MSDCPLL>, <CLK_APMIXED_NNAPLL>;
>>>>>>
>>>>>> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
>>>>>>
>>>>>> These are just examples - I don't currently know if it's a better idea to have an
>>>>>> allowlist or a blocklist as devicetree properties, as that depends on the expected
>>>>>> number of PLLs for which we en/dis fhctl or just ssc (if we generally want fhctl
>>>>>> enabled on all but one PLLs, we should use fhctl-disable, otherwise, fhctl-enable).
>>>>>
>>>>> We also have a properity "ssc-rate" for setting up the ssc rate in percentage. The "ssc-
>>>>> rate" properity is under fhctl dts node and can be setup on each fhctl-PLL.
>>>>>
>>>>
>>>> Right. For that, we could have a default sensible percentage when SSC is enabled
>>>> but no rate is set in devicetree, or we can perhaps consider SSC enabled when any
>>>> meaningful SSC rate is set... For example:
>>>>
>>>> mediatek,ssc-enable = <CLK_APMIXED_MFGPLL>, <CLK_APMIXED_TVDPLL>;
>>>> mediatek,ssc-percent = <5>, <5>;
>>>>
>>>> ... or something like:
>>>>
>>>> mediatek,ssc = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
>>>>
>>>> ...but I'd like to have some feedback on that from somebody else, as I don't know
>>>> if that would be acceptable in devicetree, or if there's any cleaner, niftier
>>>> solution.
>>>
>>> OK, we will use this:
>>>
>>> mediatek,hopping-ssc-percent = <CLK_APMIXED_MFGPLL 5>, <CLK_APMIXED_TVDPLL 5>;
>>>
>>
>> Looks good.
>>
>>>>
>>>>>>
>>>>>>>>>
>>>>>>>>>> We will set some PLL's frequency be controlled
>>>>>>>>>> by clk-pll and some are controlled by FHCTL.
>>>>>>>>>
>>>>>>>>> Another question: is this also changing on a per-board basis?
>>>>>>>>>
>>>>>>>>> (note: the pll names in the example are random and not specific to anything)
>>>>>>>>>
>>>>>>>>> Example: board A wants FHCTL on MMPLL, TVDPLL, MPLL, but *shall not* hand over
>>>>>>>>>                       NNAPLL, MFGPLL
>>>>>>>>>               board B wants FHCTL on NNAPLL, TVDPLL but *shall not* hand over MMPLL
>>>>>>>>>
>>>>>>>>> Granted that the two A, B boards are using the same SoC, can that ever happen?
>>>>>>>
>>>>>>> This could happen if A, B boards have different desense issue.
>>>>>>>
>>>>>>
>>>>>> Ok, so it's definitely board specific. Devicetree is the way to go for this.
>>>>>>
>>>>>>>>>
>>>>>>>>>> And use `perms` param to decide
>>>>>>>>>> whether a PLL is using FHCTL to change its frequency.
>>>>>>>>>
>>>>>>>>> The perms param seems to be about:
>>>>>>>>>       * Enabling debug (but you're not providing any way to actually use debugging
>>>>>>>>>         features, so what's the point?)
>>>>>>>
>>>>>>> Debugging feature is not used yet, we can removed it.
>>>>>>>
>>>>>>
>>>>>> If the debugging features of the FHCTL driver will be like what I can see on
>>>>>> the downstream MT6893 5.10 kernel, that's not really applicable to upstream.
>>>>>>
>>>>>> In that case, please remove the debug.
>>>>>
>>>>> Ok, we will remove it.
>>>>>
>>>>>>
>>>>>>>>>       * Handing over PLL control to FHCTL for hopping (can be as well done with
>>>>>>>>>         simply using a different .set_rate() callback instead of a flag)
>>>>>>>
>>>>>>> There has some PLL that have FHCTL but don't want to use FHCTL. The flag is used in
>>>>>>> this case.
>>>>>>>
>>>>>>
>>>>>> Use the flag to set the right .set_rate() callback, set at probe time, instead of
>>>>>> checking that flag at every set_rate() call.
>>>>>
>>>>> We will setup .set_rate() callback when doing fhctl-pll init.
>>>>>
>>>>>>
>>>>>>>>>       * Enabling/disabling Spread Spectrum Clocking (and I think that this is a
>>>>>>>>>         legit use for flags, but if it's just one flag, you can as well use a
>>>>>>>>>         bool and manage this with a devicetree param like "enable-ssc")
>>>>>>>>>
>>>>>>>>> That said, I think that the current way of enabling the FHCTL is more complicated
>>>>>>>>> than how it should really be.
>>>>>>>
>>>>>>> Here needs an option to decide whether to enable FHCTL-hopping or FHCTL-ssc since
>>>>>>> these two are per-board basis.
>>>>>>>
>>>>>>> We cannot force all PLL hand over to FHCTL for hopping casue not all PLLs support
>>>>>>> FHCTL and not all PLLs have need of using FHCTL-hopping.
>>>>>>>
>>>>>>
>>>>>> Board specific -> devicetree
>>>>>>
>>>>>> SoC specific -> hardcode, no devicetree.
>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> 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.
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Thing is, I don't get why you think that adding SSC to clk-pll would complicate it
>>>>>>>>> so much... it's really just a few register writes and nothing else, so I really
>>>>>>>>> don't see where the problem is, here.
>>>>>>>>>
>>>>>>>>> Another issue is that this driver may be largely incomplete, so perhaps I can't
>>>>>>>>> really see the complications you're talking about? Is this the case?
>>>>>>>>>
>>>>>>>>> Regarding keeping the FHCTL code in separated files, that's fine, but I would still
>>>>>>>>> integrate it tightly in clk-pll and its registration flow, because - yes, this is
>>>>>>>>> for sure not mandatory, but the main parameters are constant, they never change for
>>>>>>>>> a specific PLL, as they're register offsets, bits and masks (which, again, will
>>>>>>>>> never change as long as we're using the same SoC).
>>>>>>>
>>>>>>> The driver may need to supoport microP by future HW design, standalone file clk-
>>>>>>> fhctl.c helps to trigger init flow of such as ap-init-flow, microP-init-flow .....,
>>>>>>> and those different init-flow also need to run some communication API with microP.
>>>>>>> Those communication APIs are not suitable to merge into clk-pll.
>>>>>>>
>>>>>>
>>>>>> Let's use clk-fhctl as an helper then, we can make sure to call the init flow for
>>>>>> the microP in the SoC-specific clock drivers, I think that's not a problem?
>>>>>>
>>>>>> clk_mtfuturesoc_someip_probe()
>>>>>> {
>>>>>> 	.... register clocks ....
>>>>>>
>>>>>> 	freqhopping_microp_init();
>>>>>>
>>>>>> 	return ret;
>>>>>> }
>>>>>>
>>>>>> If there's hardware out there that supports such feature and a downstream kernel to
>>>>>> look at, please tell me which one, so that I will be able to check it out and
>>>>>> perhaps understand how this flow works.
>>>>>>
>>>>>> P.S.: I guess it's not fhctl-sspm?
>>>>>
>>>>> You could find clk-fhctl-mcupm.c and clk-fhctl-gpueb.c on the downstream MT6893 5.10
>>>>> kernel. Those codes require the PLL hardware specification to determine which PLL
>>>>> group(eg. PLL TOP group, GPUEB group) runs on which microP and has responsibilty to
>>>>> communicate with the microP.
>>>>>
>>>>> If we implement these things into clk-pll driver, clk-pll driver not only needs to control
>>>>> PLL frequency but also needs to deal with microP IPI. It makes clk-pll driver have others
>>>>> works that is not belong to PLL operation. That's why we tend to have a standalone driver
>>>>> for FHCTL.
>>>>>
>>>>
>>>> Ok having something to analyze made this entire thing a bit more clear in my mind,
>>>> thanks for the pointers.
>>>>
>>>> Analyzing clk-fhctl-mcupm and clk-fhctl-gpueb makes me see that there's a lot of
>>>> common code between the two: x_hopping_v1(), x_ssc_enable_v1(), x_ssc_disable_v1()
>>>> (where x = {gpueb,mcupm}) are really the same functions, duplicated and renamed
>>>> and nothing else.
>>>> The only difference is the get_xxxx_ipidev(), which is avoidable by assigning
>>>> mboxes = <...something...> in devicetree (gpueb mailbox, or mcupm mailbox).
>>>>
>>>> Even the `FH_DEVCTL_CMD_ID` enumeration uses the same values!
>>>>
>>>> To unroll that riddle, I would at that point add a new MediaTek specific clock
>>>> driver (like clk-pll) and call it `clk-ipi.c`, because that's what it does in
>>>> the end: whatever we do, goes through a mailbox instead of a direct MMIO write.
>>>>
>>>> That clk-fhctl-ipi would contain a probe function that gets the mailbox handle,
>>>> then we would add something like `clk_fhctl_set_rate()` function, export it in
>>>> the `clk-mtk.h` or in a new `clk-fhctl.h` header, then assign the right callback
>>>> in either the SoC's clock driver (by registering a different clock type, which,
>>>> in this case, would be clk-fhctl-ipi instead of clk-pll), or in clk-pll itself...
>>>>
>>>> In the end, I'm effectively proposing to:
>>>>
>>>> 1. Merge the direct-MMIO handling of FHCTL in clk-pll;
>>>> 2. Create a new driver (and clock type, eventually) for the IPI handling of FHCTL.
>>>
>>>   From your idea, I think we can also create a new clock type for fhctl such as clk-pll-fh
>>> and add a new PLL register function for PLL+FHCTL. Then we can change the registery
>>> interface and won't affect the legacy ICs. Also, if FHCTL has changes, we only need to
>>> modify clk-pll-fh.
>>> I think using a new clock type has extendibility for FHCTL changes and also compatiable
>>> with legacy ICs.
>>>
>>> clk-pll.h
>>> 	/* Define FHCTL data structure and contains mtk_pll_data.
>>> 	 * We can use mtk_pll_data later. */
>>> 	mtk_pll_fh_data {
>>> 		struct mtk_pll_data pll_data;
>>> 		/* fhctl_data */
>>> 		unsigned int fh_id;
>>> 		unsigned int ssc_rate;
>>> 		...
>>> 	}
>>>
>>> clk-mt8186-apmixedsys.c
>>> 	func clk_mt8186_apmixed_probe()
>>> 		/* There are two implementations.
>>> 		 * If ICs need FHCTL such as MT8186, use mtk_clk_register_pllfhs()
>>> 		 * For those legacy ICs which don't need FHCTL, still use
>>> 		 * mtk_clk_register_plls().
>>> 		 */
>>> 		/* 1. Need FHCTL. Use API from clk-pll-fh.c */
>>> 		fhctl_parse_dt()
>>> 		mtk_clk_register_pllfhs(plls data, fh-plls data)
>>> 		
>>> 		/* 2. Legacy ICs. Use API from clk-pll.c */
>>> 		mtk_clk_register_plls()
>>
>> I'm not sure if we're saying the very same thing here, but for the sake of being
>> clear and avoiding any misunderstanding, here's my description.
>>
>>
>> We should call both functions: register_pllfhs() for the PLLs that have support for
>> freqhopping, register_plls() for the ones that *do not support* freqhopping.
>>
>> Example for PLL_A, PLL_B, PLL_C, PLL_D:
>>
>> Freqhopping supported (enabled or not): PLL_A, PLL_B
>> Freqhopping NOT supported at all: PLL_C, PLL_D
>>
>> fhplls_data[] = { PLL_A, PLL_B };
>> plls_data[] = { PLL_C, PLL_D };
>>
>> func mtk_clk_register_pllfhs(fhdata)
>> 	walk through fhplls_data only, other plls are not passed to this function
>>
>> func clk_mt8186_apmixed_probe()
>> 	/* Some PLLs must be controlled directly via MMIO, while others
>> 	 * support Frequency Hopping through FHCTL.
>> 	 * Where FHCTL is supported, register clock with register_pllfhs.
>> 	 * PLLs that are not supported by FHCTL: register with register_plls.
>> 	 */
>>
>> 	/* Register FHCTL PLLs */
>> 	fhctl_parse_dt()
>> 	mtk_clk_register_pllfhs(array of plls supporting pllfh)
>>
>> 	/* Register the PLLs that do not support FHCTL at all */
>> 	mtk_clk_register_plls(all the other PLLs that cannot be FHCTL-controlled)
>>
> 
> I have a different opinion here. I think we should just choose one register function when
> probe and it depends on platform implementation instead of a PLL is supported FHCTL or
> not. We have an option to use clk-pll-fh or clk-pll at the beginning of new IC
> development. And, we want it's easy to add FHCTL support on legecy ICs.
> 
> The way of how we use the register function is affected by the way we use fhplls_data. If
> FHCTL supported or not is used to decide which registery function should we use, it has to
> change the exist plls_data. But most of IC's PLL data is already exist. So I want to use a
> structure to descript FHCTL HW itself and have a link to existed PLL data.
> 
>  From your example, it will look like:
> 
> plls_data = { PLL_A, PLL_B, PLL_C, PLL_D };  /* Already existed */
> fhplls_data = { FH_A,
> FH_B };  /* New added for FHCTL data */
> 
> func mtk_clk_register_pllfhs(plls_data, fhplls_data)
> 	fhctl_match_pll_data()
> 		/* Link fhpll and pll:
> 		 *   FH_A -> PLL_A
> 		 *   FH_B -> PLL_B
> 		 */
> 
> For MT8186 and ICs need FHCTL supported:
> 	func clk_mt8186_apmixed_probe()
> 		fhctl_parse_dt()
> 		mtk_clk_register_pllfhs(plls_data, fhplls_data)
> 
> For those don't need FHCTL and legacy ICs:
> 	func clk_mtxxxx_apmixed_probe()
> 		mtk_clk_register_plls(plls_data)
> 
> When one ICs that need to support FHCTL but they didn't implement before, we only need to
> change the registery function and add FHCTL data, no need to modify existed plls_data. I
> think it can save our development time and maintenance effort.
> 

I see what you mean now. Okay, that seems sensible.

>>>
>>> clk-pll.c
>>> 	/* No functional changes, so legacy ICs won't be affected.
>>> 	 * Export clk_ops functions to clk-pll-fh.c
>>> 	 */
>>> 	func mtk_clk_register_plls()
>>> 		init.ops = &mtk_pll_ops;
>>>
>>> clk-pll-fh.c
>>> 	/* A clock type of FHCTL PLL. Used to setup HW data and ops.
>>> 	 * Most of ops functions inherit from clk-pll.c.
>>> 	 * If PLL not support or not enable FHCTL, fallback to use &mtk_pll_ops.
>>> 	 */
>>> 	func mtk_clk_register_pllfhs(plls data, fh-plls data)
>>> 		fhctl_match_pll_data()  /* match mtk_pll_data and mtk_pll_fh_data */
>>> 		fhctl_hw_init()
>>> 		if (fhctl_is_supported_and_enabled(pll))
>>
>> Overall, this seems to look good, minor one nit: if FHCTL is *not supported* on
>> a PLL, we should *not* even call mtk_clk_register_pllfhs on that PLL, so your
>> pseudocode would be just:
>>
>> 		if (fhctl_is_enabled(pll))
> 
> The requirements of clk-pll-fh are:
> 1. It is compatible with those PLLs that don't support FHCTL HW.
> 2. It can handle PLLs that supported FHCTL but aren't enabled.
> 3. It can handle PLLs that supported FHCTL but are enabled.
> 
> So, I use fhctl_is_supported_and_enabled(PLL) here. No matter a PLL supports FHCTL or not,
> it can work.
> 

Alright, let's go with that!

>>
>>> 			init.ops = &mtk_pll_fhctl_ops;
>>> 		else
>>> 			init.ops = &mtk_pll_ops;
>>> 	
>>> 		if (ssc_is_enable(pll))
>>> 			fhctl_ssc_enable(pll)
>>>
>>> clk-fhctl.c
>>> 	/* APIs of FHCTL HW operations */
>>> 	func fhctl_hw_init()
>>> 	func fhctl_hopping()
>>> 	func fhctl_ssc_enable()
>>>
>>>
>>
>> So it seems that we've reached an agreement here, this was a nice planning
>> discussion; we should now have a nice and solid base to work on, which is
>> great.
>>
>> Cheers,
>> Angelo
>
diff mbox series

Patch

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"
+	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);
+
+		/* 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;
+
+	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);
+
+	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];
+}
+
+static void __set_postdiv(struct fh_pll_regs *regs, struct fh_pll_data *data,
+			  int postdiv)
+{
+	unsigned int regval, temp;
+
+	for (regval = 0 ; regval < data->postdiv_table_size ; regval++) {
+		if (data->postdiv_table[regval] > postdiv) {
+			regval--;
+			break;
+		}
+	}
+
+	temp = (readl(regs->reg_con_postdiv)) & ~(data->postdiv_mask);
+	temp |= regval << data->postdiv_offset;
+	writel(temp, regs->reg_con_postdiv);
+}
+
+static int fhctl_ap_hopping(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;
+	int ret = 0;
+	struct hdlr_data *d = (struct hdlr_data *)priv_data;
+	spinlock_t *lock = d->lock;
+	unsigned long flags = 0;
+	unsigned int pll_postdiv;
+
+	domain = d->domain;
+	regs = &domain->regs[fh_id];
+	data = &domain->data[fh_id];
+
+	if (postdiv > 0) {
+		pll_postdiv = __get_postdiv(regs, data);
+
+		if (postdiv > pll_postdiv)
+			__set_postdiv(regs, data, postdiv);
+	}
+
+	spin_lock_irqsave(lock, flags);
+
+	ret = hopping_hw_flow(priv_data, domain_name, fh_id, new_dds, postdiv);
+
+	spin_unlock_irqrestore(lock, flags);
+
+	if (postdiv > 0) {
+		if (postdiv < pll_postdiv)
+			__set_postdiv(regs, data, postdiv);
+	}
+
+	return ret;
+}
+
+static int fhctl_ap_ssc_enable(void *priv_data, char *domain_name,
+			       int fh_id, int rate)
+{
+	struct fh_pll_domain *domain;
+	struct fh_pll_regs *regs;
+	struct fh_pll_data *data;
+	struct hdlr_data *d = (struct hdlr_data *)priv_data;
+	spinlock_t *lock = d->lock;
+	struct pll_dts *array = d->array;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(lock, flags);
+
+	domain = d->domain;
+	regs = &domain->regs[fh_id];
+	data = &domain->data[fh_id];
+
+	fhctl_set_ssc_regs(regs, data, fh_id, rate);
+
+	array->ssc_rate = rate;
+
+	spin_unlock_irqrestore(lock, flags);
+
+	return 0;
+}
+
+static int fhctl_ap_ssc_disable(void *priv_data, char *domain_name, int fh_id)
+{
+	struct fh_pll_domain *domain;
+	struct fh_pll_regs *regs;
+	struct fh_pll_data *data;
+	struct hdlr_data *d = (struct hdlr_data *)priv_data;
+	spinlock_t *lock = d->lock;
+	struct pll_dts *array = d->array;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(lock, flags);
+
+	domain = d->domain;
+	regs = &domain->regs[fh_id];
+	data = &domain->data[fh_id];
+
+	fhctl_set_ssc_regs(regs, data, fh_id, 0);
+
+	array->ssc_rate = 0;
+
+	spin_unlock_irqrestore(lock, flags);
+
+	return 0;
+}
+
+static int fhctl_ap_hw_init(struct pll_dts *array, struct fh_ap_match *match)
+{
+	static DEFINE_SPINLOCK(lock);
+	struct hdlr_data *priv_data;
+	struct fh_hdlr *hdlr;
+	struct fh_pll_domain *domain;
+	int fh_id = array->fh_id;
+	struct fh_pll_regs *regs;
+	struct fh_pll_data *data;
+	int mask = BIT(fh_id);
+
+	priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
+	hdlr = kzalloc(sizeof(*hdlr), GFP_KERNEL);
+	init_fh_domain(array->domain, array->comp, array->fhctl_base,
+		       array->apmixed_base);
+
+	priv_data->array = array;
+	priv_data->lock = &lock;
+	priv_data->domain = get_fh_domain(array->domain);
+
+	/* do HW init */
+	domain = priv_data->domain;
+	regs = &domain->regs[fh_id];
+	data = &domain->data[fh_id];
+
+	fh_set_field(regs->reg_clk_con, mask, 1);
+	fh_set_field(regs->reg_rst_con, mask, 0);
+	fh_set_field(regs->reg_rst_con, mask, 1);
+	writel(0x0, regs->reg_cfg);
+	writel(0x0, regs->reg_updnlmt);
+	writel(0x0, regs->reg_dds);
+
+	/* hook to array */
+	hdlr->data = priv_data;
+	hdlr->ops = match->hdlr->ops;
+	/* hook hdlr to array is the last step */
+	mb();
+	array->hdlr = hdlr;
+
+	/* do SSC */
+	if (array->ssc_rate) {
+		struct fh_hdlr *hdlr = array->hdlr;
+
+		hdlr->ops->ssc_enable(hdlr->data, array->domain, array->fh_id,
+				      array->ssc_rate);
+	}
+
+	return 0;
+}
+
+static struct fh_operation fhctl_ap_ops = {
+	.hopping = fhctl_ap_hopping,
+	.ssc_enable = fhctl_ap_ssc_enable,
+	.ssc_disable = fhctl_ap_ssc_disable,
+};
+
+static struct fh_hdlr mt8186_hdlr = {
+	.ops = &fhctl_ap_ops,
+};
+
+static struct fh_ap_match mt8186_match = {
+	.name = "mediatek,mt8186-fhctl",
+	.hdlr = &mt8186_hdlr,
+	.init = &fhctl_ap_hw_init,
+};
+
+static struct fh_ap_match *matches[] = {
+	&mt8186_match,
+	NULL,
+};
+
+int fhctl_ap_init(struct pll_dts *array)
+{
+	int i;
+	int num_pll = array->num_pll;
+	struct fh_ap_match **match = matches;
+
+	/* find match by compatible */
+	for (i = 0; i < ARRAY_SIZE(matches); i++) {
+		char *comp = (*match)->name;
+		char *target = array->comp;
+
+		if (!strcmp(comp, target))
+			break;
+		match++;
+	}
+
+	if (*match == NULL)
+		return -1;
+
+	/* init flow for every pll */
+	for (i = 0; i < num_pll; i++, array++) {
+		char *method = array->method;
+
+		if (!strcmp(method, FHCTL_TARGET))
+			(*match)->init(array, *match);
+	}
+
+	return 0;
+}
diff --git a/drivers/clk/mediatek/clk-fhctl-pll.c b/drivers/clk/mediatek/clk-fhctl-pll.c
new file mode 100644
index 000000000000..b3ccbbd04e1b
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl-pll.c
@@ -0,0 +1,209 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include "clk-fhctl-pll.h"
+#include "clk-fhctl-util.h"
+
+#define REG_ADDR(base, x) ((void __iomem *)((unsigned long)base + (x)))
+
+struct fh_pll_match {
+	char *compatible;
+	struct fh_pll_domain **domain_list;
+};
+
+static int fhctl_pll_init(struct fh_pll_domain *d, void __iomem *fhctl_base,
+			  void __iomem *apmixed_base)
+{
+	struct fh_pll_data *data = d->data;
+	struct fh_pll_offset *offset = d->offset;
+	struct fh_pll_regs *regs = d->regs;
+
+	if (regs->reg_hp_en)
+		return 0;
+
+	while (data->dds_mask != 0) {
+		int regs_offset;
+
+		/* fhctl common part */
+		regs->reg_hp_en = REG_ADDR(fhctl_base, offset->offset_hp_en);
+		regs->reg_clk_con = REG_ADDR(fhctl_base,
+					     offset->offset_clk_con);
+		regs->reg_rst_con = REG_ADDR(fhctl_base,
+					     offset->offset_rst_con);
+		regs->reg_slope0 = REG_ADDR(fhctl_base, offset->offset_slope0);
+		regs->reg_slope1 = REG_ADDR(fhctl_base, offset->offset_slope1);
+
+		/* fhctl pll part */
+		regs_offset = offset->offset_fhctl + offset->offset_cfg;
+		regs->reg_cfg = REG_ADDR(fhctl_base, regs_offset);
+		regs->reg_updnlmt = REG_ADDR(regs->reg_cfg,
+					     offset->offset_updnlmt);
+		regs->reg_dds = REG_ADDR(regs->reg_cfg, offset->offset_dds);
+		regs->reg_dvfs = REG_ADDR(regs->reg_cfg, offset->offset_dvfs);
+		regs->reg_mon = REG_ADDR(regs->reg_cfg, offset->offset_mon);
+
+		/* apmixed part */
+		regs->reg_con_pcw = REG_ADDR(apmixed_base,
+					     offset->offset_con_pcw);
+		regs->reg_con_postdiv = REG_ADDR(apmixed_base,
+						 offset->offset_con_postdiv);
+
+		data++;
+		offset++;
+		regs++;
+	}
+
+	return 0;
+}
+
+static unsigned int __postdiv_pow_tbl[8] = {1, 2, 4, 8, 16, 1, 1, 1};
+#define POSTDIV_TABLE_SIZE (sizeof(__postdiv_pow_tbl)\
+	/sizeof(unsigned int))
+
+#define SIZE_8186_TOP (sizeof(mt8186_top_data)\
+	/sizeof(struct fh_pll_data))
+#define DATA_8186_TOP(_name) {						\
+		.name = _name,						\
+		.dds_mask = GENMASK(21, 0),				\
+		.postdiv_mask = GENMASK(26, 24),			\
+		.postdiv_offset = 24,					\
+		.postdiv_table = __postdiv_pow_tbl,			\
+		.postdiv_table_size = POSTDIV_TABLE_SIZE,		\
+		.slope0_value = 0x6003c97,				\
+		.slope1_value = 0x6003c97,				\
+		.sfstrx_en = BIT(2),					\
+		.frddsx_en = BIT(1),					\
+		.fhctlx_en = BIT(0),					\
+		.tgl_org = BIT(31),					\
+		.dvfs_tri = BIT(31),					\
+		.pcwchg = BIT(31),					\
+		.dt_val = 0x0,						\
+		.df_val = 0x9,						\
+		.updnlmt_shft = 16,					\
+		.msk_frddsx_dys = GENMASK(23, 20),			\
+		.msk_frddsx_dts = GENMASK(19, 16),			\
+	}
+#define OFFSET_8186_TOP(_fhctl, _con_pcw) {				\
+		.offset_fhctl = _fhctl,					\
+		.offset_con_pcw = _con_pcw,				\
+		.offset_con_postdiv = _con_pcw,				\
+		.offset_hp_en = 0x0,					\
+		.offset_clk_con = 0x8,					\
+		.offset_rst_con = 0xc,					\
+		.offset_slope0 = 0x10,					\
+		.offset_slope1 = 0x14,					\
+		.offset_cfg = 0x0,					\
+		.offset_updnlmt = 0x4,					\
+		.offset_dds = 0x8,					\
+		.offset_dvfs = 0xc,					\
+		.offset_mon = 0x10,					\
+	}
+static struct fh_pll_data mt8186_top_data[] = {
+	DATA_8186_TOP("armpll_ll"),
+	DATA_8186_TOP("armpll_bl"),
+	DATA_8186_TOP("ccipll"),
+	DATA_8186_TOP("mainpll"),
+	DATA_8186_TOP("mmpll"),
+	DATA_8186_TOP("tvdpll"),
+	DATA_8186_TOP("mpll"),
+	DATA_8186_TOP("adsppll"),
+	DATA_8186_TOP("mfgpll"),
+	DATA_8186_TOP("nnapll"),
+	DATA_8186_TOP("nna2pll"),
+	DATA_8186_TOP("msdcpll"),
+	DATA_8186_TOP("mempll"),
+	{}
+};
+static struct fh_pll_offset mt8186_top_offset[] = {
+	OFFSET_8186_TOP(0x003C, 0x0208),
+	OFFSET_8186_TOP(0x0050, 0x0218),
+	OFFSET_8186_TOP(0x0064, 0x0228),
+	OFFSET_8186_TOP(0x0078, 0x0248),
+	OFFSET_8186_TOP(0x008C, 0x0258),
+	OFFSET_8186_TOP(0x00A0, 0x0268),
+	OFFSET_8186_TOP(0x00B4, 0x0278),
+	OFFSET_8186_TOP(0x00C8, 0x0308),
+	OFFSET_8186_TOP(0x00DC, 0x0318),
+	OFFSET_8186_TOP(0x00F0, 0x0360),
+	OFFSET_8186_TOP(0x0104, 0x0370),
+	OFFSET_8186_TOP(0x0118, 0x0390),
+	OFFSET_8186_TOP(0x012c, 0xdeb1),
+	{}
+};
+static struct fh_pll_regs mt8186_top_regs[SIZE_8186_TOP];
+static struct fh_pll_domain mt8186_top = {
+	.name = "top",
+	.data = (struct fh_pll_data *)&mt8186_top_data,
+	.offset = (struct fh_pll_offset *)&mt8186_top_offset,
+	.regs = (struct fh_pll_regs *)&mt8186_top_regs,
+	.init = &fhctl_pll_init,
+};
+static struct fh_pll_domain *mt8186_domain[] = {
+	&mt8186_top,
+	NULL,
+};
+static struct fh_pll_match mt8186_match = {
+	.compatible = "mediatek,mt8186-fhctl",
+	.domain_list = (struct fh_pll_domain **)mt8186_domain,
+};
+
+static const struct fh_pll_match *matches[] = {
+	&mt8186_match,
+	NULL
+};
+
+
+static struct fh_pll_domain **get_list(char *comp)
+{
+	struct fh_pll_match **match;
+	static struct fh_pll_domain **list;
+	int i;
+
+	match = (struct fh_pll_match **)matches;
+
+	/* name used only if !list */
+	if (!list) {
+		for (i = 0; i < ARRAY_SIZE(matches); i++) {
+			if (!strcmp(comp, (*match)->compatible)) {
+				list = (*match)->domain_list;
+				break;
+			}
+			match++;
+		}
+	}
+	return list;
+}
+void init_fh_domain(const char *domain, char *comp, void __iomem *fhctl_base,
+		    void __iomem *apmixed_base)
+{
+	struct fh_pll_domain **list;
+
+	list = get_list(comp);
+
+	while (*list != NULL) {
+		if (!strcmp(domain, (*list)->name)) {
+			(*list)->init(*list, fhctl_base, apmixed_base);
+			return;
+		}
+		list++;
+	}
+}
+
+struct fh_pll_domain *get_fh_domain(const char *domain)
+{
+	struct fh_pll_domain **list;
+
+	list = get_list(NULL);
+
+	/* find instance */
+	while (*list != NULL) {
+		if (!strcmp(domain, (*list)->name))
+			return *list;
+		list++;
+	}
+	return NULL;
+}
diff --git a/drivers/clk/mediatek/clk-fhctl-pll.h b/drivers/clk/mediatek/clk-fhctl-pll.h
new file mode 100644
index 000000000000..7f0f7577f7a5
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl-pll.h
@@ -0,0 +1,74 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#ifndef __CLK_FHCTL_PLL_H
+#define __CLK_FHCTL_PLL_H
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+
+struct fh_pll_data {
+	char *name;
+	unsigned int dds_mask;
+	unsigned int postdiv_mask;
+	unsigned int postdiv_offset;
+	unsigned int *postdiv_table;
+	unsigned int postdiv_table_size;
+	unsigned int slope0_value;
+	unsigned int slope1_value;
+	unsigned int sfstrx_en;
+	unsigned int frddsx_en;
+	unsigned int fhctlx_en;
+	unsigned int tgl_org;
+	unsigned int dvfs_tri;
+	unsigned int pcwchg;
+	unsigned int dt_val;
+	unsigned int df_val;
+	unsigned int updnlmt_shft;
+	unsigned int msk_frddsx_dys;
+	unsigned int msk_frddsx_dts;
+};
+struct fh_pll_offset {
+	int offset_fhctl;
+	int offset_con_pcw;
+	int offset_con_postdiv;
+	int offset_hp_en;
+	int offset_clk_con;
+	int offset_rst_con;
+	int offset_slope0;
+	int offset_slope1;
+	int offset_cfg;
+	int offset_updnlmt;
+	int offset_dds;
+	int offset_dvfs;
+	int offset_mon;
+};
+struct fh_pll_regs {
+	void __iomem *reg_hp_en;
+	void __iomem *reg_clk_con;
+	void __iomem *reg_rst_con;
+	void __iomem *reg_slope0;
+	void __iomem *reg_slope1;
+	void __iomem *reg_cfg;
+	void __iomem *reg_updnlmt;
+	void __iomem *reg_dds;
+	void __iomem *reg_dvfs;
+	void __iomem *reg_mon;
+	void __iomem *reg_con_pcw;
+	void __iomem *reg_con_postdiv;
+};
+struct fh_pll_domain {
+	char *name;
+	struct fh_pll_data *data;
+	struct fh_pll_offset *offset;
+	struct fh_pll_regs *regs;
+	int (*init)(struct fh_pll_domain *d, void __iomem *fhctl_base,
+		    void __iomem *apmixed_base);
+};
+extern struct fh_pll_domain *get_fh_domain(const char *name);
+extern void init_fh_domain(const char *domain_name, char *comp_name,
+			   void __iomem *fhctl_base,
+			   void __iomem *apmixed_base);
+#endif
diff --git a/drivers/clk/mediatek/clk-fhctl-util.h b/drivers/clk/mediatek/clk-fhctl-util.h
new file mode 100644
index 000000000000..824ed94b9f79
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl-util.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ * Author: Yu-Chang Wang <Yu-Chang.Wang@mediatek.com>
+ */
+
+#ifndef __CLK_FHCTL_UTIL_H
+#define __CLK_FHCTL_UTIL_H
+
+#define fh_set_field(reg, field, val) \
+do { \
+	unsigned int tv = readl(reg); \
+	tv &= ~(field); \
+	tv |= ((val) << (ffs(field) - 1)); \
+	writel(tv, reg); \
+} while (0)
+
+#define fh_get_field(reg, field, val) \
+do { \
+	unsigned int tv = readl(reg); \
+	val = ((tv & (field)) >> (ffs(field) - 1)); \
+} while (0)
+
+#endif
diff --git a/drivers/clk/mediatek/clk-fhctl.c b/drivers/clk/mediatek/clk-fhctl.c
new file mode 100644
index 000000000000..606245f84d71
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl.c
@@ -0,0 +1,191 @@ 
+// 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/list.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include "clk-fhctl.h"
+#include "clk-fhctl-util.h"
+#include "clk-mtk.h"
+
+static struct pll_dts *_array;
+
+bool mtk_clk_try_freq_hopping(const char *pll_name, unsigned long dds,
+			      int postdiv)
+{
+	int i;
+	struct fh_hdlr *hdlr = NULL;
+	struct pll_dts *array;
+	int num_pll;
+
+	if (_array == NULL)
+		return false;
+
+	array = _array;
+	num_pll = array->num_pll;
+
+	for (i = 0; i < num_pll; i++, array++) {
+		if (!strcmp(pll_name, array->pll_name)) {
+			hdlr = array->hdlr;
+			break;
+		}
+	}
+
+	if (hdlr && (array->perms & PERM_DRV_HOP)) {
+		hdlr->ops->hopping(hdlr->data, array->domain, array->fh_id,
+				   dds, postdiv);
+		return true;
+	}
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(mtk_clk_try_freq_hopping);
+
+static struct pll_dts *parse_dt(struct platform_device *pdev)
+{
+	struct device_node *root, *map, *of_pll;
+	unsigned int num_pll = 0;
+	int iomap_idx = 0;
+	struct pll_dts *array;
+	int pll_idx = 0;
+	const struct of_device_id *match;
+
+	root = pdev->dev.of_node;
+	match = of_match_node(pdev->dev.driver->of_match_table, root);
+
+	/* iterate dts to get pll count */
+	for_each_child_of_node(root, map) {
+		for_each_child_of_node(map, of_pll) {
+			num_pll++;
+		}
+	}
+
+	array = kzalloc(sizeof(*array) * num_pll, GFP_KERNEL);
+
+	for_each_child_of_node(root, map) {
+		void __iomem *fhctl_base, *apmixed_base;
+		char *domain, *method;
+		int num = 0;
+
+		fhctl_base = of_iomap(root, iomap_idx++);
+		apmixed_base = of_iomap(root, iomap_idx++);
+		of_property_read_string(map, "domain", (const char **)&domain);
+		of_property_read_string(map, "method", (const char **)&method);
+
+		for_each_child_of_node(map, of_pll) {
+			int fh_id, perms, ssc_rate;
+
+			if (pll_idx >= num_pll) {
+				pll_idx++;
+				continue;
+			}
+
+			/* default for optional field */
+			perms = 0xffffffff;
+			ssc_rate = 0;
+
+			of_property_read_u32(of_pll, "fh-id", &fh_id);
+			of_property_read_u32(of_pll, "perms", &perms);
+			of_property_read_u32(of_pll, "ssc-rate", &ssc_rate);
+			array[pll_idx].num_pll = num_pll;
+			array[pll_idx].comp = (char *)match->compatible;
+			array[pll_idx].pll_name = (char *)of_pll->name;
+			array[pll_idx].fh_id = fh_id;
+			array[pll_idx].perms = perms;
+			array[pll_idx].ssc_rate = ssc_rate;
+			array[pll_idx].domain = domain;
+			array[pll_idx].method = method;
+			array[pll_idx].fhctl_base = fhctl_base;
+			array[pll_idx].apmixed_base = apmixed_base;
+			num++;
+			pll_idx++;
+		}
+	}
+
+	return array;
+}
+
+static int fh_plt_drv_probe(struct platform_device *pdev)
+{
+	int i, ret;
+	struct pll_dts *array;
+
+	/* convert dt to data */
+	array = parse_dt(pdev);
+
+	ret = fhctl_ap_init(array);
+	if (ret)
+		return -1;
+
+	/* make sure array is complete */
+	for (i = 0; i < array->num_pll; i++) {
+		struct fh_hdlr *hdlr = array[i].hdlr;
+
+		if (!hdlr) {
+			dev_err(&pdev->dev, "Failed to set %s hdlr\n",
+				array->pll_name);
+			return -1;
+		}
+	}
+
+	/* make sure init complete */
+	mb();
+	_array = array;
+
+	return 0;
+}
+
+static void fh_plt_drv_shutdown(struct platform_device *pdev)
+{
+	struct pll_dts *array = _array;
+	int num_pll = array->num_pll;
+	int i;
+
+	for (i = 0; i < num_pll; i++, array++) {
+		struct fh_hdlr *hdlr = array->hdlr;
+
+		if (array->ssc_rate)
+			hdlr->ops->ssc_disable(hdlr->data, array->domain,
+					       array->fh_id);
+	}
+}
+
+static const struct of_device_id fh_of_match[] = {
+	{ .compatible = "mediatek,mt8186-fhctl"},
+	{}
+};
+
+static struct platform_driver fhctl_driver = {
+	.probe = fh_plt_drv_probe,
+	.shutdown = fh_plt_drv_shutdown,
+	.driver = {
+		.name = "mtk-fhctl",
+		.owner = THIS_MODULE,
+		.of_match_table = fh_of_match,
+	},
+};
+
+static int __init fhctl_driver_init(void)
+{
+	return platform_driver_register(&fhctl_driver);
+}
+device_initcall_sync(fhctl_driver_init);
+
+static void __exit fhctl_driver_exit(void)
+{
+	platform_driver_unregister(&fhctl_driver);
+}
+module_exit(fhctl_driver_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek FHCTL Driver");
+MODULE_AUTHOR("Kuan-Hsin Lee <kuan-hsin.lee@mediatek.com>");
diff --git a/drivers/clk/mediatek/clk-fhctl.h b/drivers/clk/mediatek/clk-fhctl.h
new file mode 100644
index 000000000000..b53a99d6cac7
--- /dev/null
+++ b/drivers/clk/mediatek/clk-fhctl.h
@@ -0,0 +1,45 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#ifndef __CLK_FHCTL_H
+#define __CLK_FHCTL_H
+
+struct fh_operation {
+	int (*hopping)(void *data, char *domain, int fh_id,
+		       unsigned int new_dds, int postdiv);
+	int (*ssc_enable)(void *data, char *domain, int fh_id, int rate);
+	int (*ssc_disable)(void *data, char *domain, int fh_id);
+};
+
+struct fh_hdlr {
+	void *data;
+	struct fh_operation *ops;
+};
+
+struct pll_dts {
+	char *comp;
+	int num_pll;
+	char *domain;
+	char *method;
+	char *pll_name;
+	int fh_id;
+	int perms;
+	int ssc_rate;
+	void __iomem *fhctl_base;
+	void __iomem *apmixed_base;
+	struct fh_hdlr *hdlr;
+};
+
+#define PERM_DRV_HOP BIT(0)
+#define PERM_DRV_SSC BIT(1)
+#define PERM_DBG_HOP BIT(2)
+#define PERM_DBG_SSC BIT(3)
+#define PERM_DBG_DUMP BIT(4)
+
+#define FHCTL_AP "fhctl-ap"
+
+extern int fhctl_ap_init(struct pll_dts *array);
+
+#endif
diff --git a/drivers/clk/mediatek/clk-pll.c b/drivers/clk/mediatek/clk-pll.c
index 54e6cfd29dfc..1acd21ca4b93 100644
--- a/drivers/clk/mediatek/clk-pll.c
+++ b/drivers/clk/mediatek/clk-pll.c
@@ -206,7 +206,10 @@  static int mtk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 	u32 postdiv;
 
 	mtk_pll_calc_values(pll, &pcw, &postdiv, rate, parent_rate);
-	mtk_pll_set_rate_regs(pll, pcw, postdiv);
+#ifdef CONFIG_COMMON_CLK_MTK_FREQ_HOPPING
+	if (!mtk_clk_try_freq_hopping(pll->data->name, pcw, postdiv))
+#endif
+		mtk_pll_set_rate_regs(pll, pcw, postdiv);
 
 	return 0;
 }
diff --git a/drivers/clk/mediatek/clk-pll.h b/drivers/clk/mediatek/clk-pll.h
index fe3199715688..e95f5f48f308 100644
--- a/drivers/clk/mediatek/clk-pll.h
+++ b/drivers/clk/mediatek/clk-pll.h
@@ -54,4 +54,9 @@  int mtk_clk_register_plls(struct device_node *node,
 void mtk_clk_unregister_plls(const struct mtk_pll_data *plls, int num_plls,
 			     struct clk_hw_onecell_data *clk_data);
 
+#ifdef CONFIG_COMMON_CLK_MTK_FREQ_HOPPING
+extern bool mtk_clk_try_freq_hopping(const char *pll_name, unsigned long dds,
+				     int postdiv);
+#endif
+
 #endif /* __DRV_CLK_MTK_PLL_H */