Re: [PATCH v4 2/2] memory/mediatek: Add an interface to get current DDR data rate

Crystal Guo (郭晶)
2025-05-12T07:37:01Z

On Wed, 2025-04-09 at 15:42 +0800, crystal guo wrote:
> >

> On Fri, 2025-04-04 at 13:11 +0200, Krzysztof Kozlowski wrote:
> > External email : Please do not click links or open attachments
> > until
> > you have verified the sender or the content.
> >
> >
> > Please use subject prefixes matching the subsystem. You can get
> > them
> > for
> > example with 'git log --oneline -- DIRECTORY_OR_FILE' on the
> > directory
> > your patch is touching. For bindings, the preferred subjects are
> > explained here:
> >
>
>
https://urldefense.com/v3/__https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html*i-for-patch-submitters__;Iw!!CTRNKA9wMg0ARbw!i9YnJLxKI_KuaW2pzwPR_GjZT8IzcZVycscosWKhQjWxvSouP0La_YLNk6lFRLpwDSUbgg8EGbZjbOjX$
> >
> > memory: mediatek:
>
> Thanks for the suggestion, I will use the correct subject prefixes in
> the next version:
> 'memory: mediatek: Add an interface to get current DDR data rate'
>
> >
> > On Thu, Apr 03, 2025 at 02:48:48PM GMT, Crystal Guo wrote:
> > > Add MediaTek DRAMC driver to provide an interface that can
> > > obtain current DDR data rate.
> > >
> > > Signed-off-by: Crystal Guo <crystal.guo@mediatek.com>
> > > ---
> > > drivers/memory/Kconfig | 1 +
> > > drivers/memory/Makefile | 1 +
> > > drivers/memory/mediatek/Kconfig | 20 +++
> > > drivers/memory/mediatek/Makefile | 2 +
> > > drivers/memory/mediatek/mtk-dramc.c | 223
> > > ++++++++++++++++++++++++++++
> > > 5 files changed, 247 insertions(+)
> > > create mode 100644 drivers/memory/mediatek/Kconfig
> > > create mode 100644 drivers/memory/mediatek/Makefile
> > > create mode 100644 drivers/memory/mediatek/mtk-dramc.c
> > >
> > > diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig
> > > index c82d8d8a16ea..b1698549ff81 100644
> > > --- a/drivers/memory/Kconfig
> > > +++ b/drivers/memory/Kconfig
> > > @@ -227,5 +227,6 @@ config STM32_FMC2_EBI
> > >
> > > source "drivers/memory/samsung/Kconfig"
> > > source "drivers/memory/tegra/Kconfig"
> > > +source "drivers/memory/mediatek/Kconfig"
> >
> > m goes before s, so this goes before samsung source.
> >
>
> Okay, I will update it in the next version.
>
> > >
> > > endif
> > > diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile
> > > index d2e6ca9abbe0..c0facf529803 100644
> > > --- a/drivers/memory/Makefile
> > > +++ b/drivers/memory/Makefile
> > > @@ -27,6 +27,7 @@ obj-$(CONFIG_STM32_FMC2_EBI) += stm32-
> > > fmc2-ebi.o
> > >
> > > obj-$(CONFIG_SAMSUNG_MC) += samsung/
> > > obj-$(CONFIG_TEGRA_MC) += tegra/
> > > +obj-$(CONFIG_MEDIATEK_MC) += mediatek/
> > > obj-$(CONFIG_TI_EMIF_SRAM) += ti-emif-sram.o
> > > obj-$(CONFIG_FPGA_DFL_EMIF) += dfl-emif.o
> > >
> > > diff --git a/drivers/memory/mediatek/Kconfig
> > > b/drivers/memory/mediatek/Kconfig
> > > new file mode 100644
> > > index 000000000000..eadc11ec0f1c
> > > --- /dev/null
> > > +++ b/drivers/memory/mediatek/Kconfig
> > > @@ -0,0 +1,20 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +config MEDIATEK_MC
> > > + bool "MediaTek Memory Controller support"
> > > + default y
> >
> > Why this has to be enabled for every compile test build? Look how
> > other
> > platforms do it.
> >
> > I'll fix the tegra.
> >
>
> Okay, I will change 'default Y' to 'default ARCH_MEDIATEK' in the
> next
> version.
>
> >
> > > + depends on ARCH_MEDIATEK || COMPILE_TEST
> > > + help
> > > + This option allows to enable MediaTek memory controller
> > > drivers,
> > > + which may include controllers for DRAM or others.
> > > +
> > > +if MEDIATEK_MC
> > > +
> > > +config MTK_DRAMC
> > > + tristate "MediaTek DRAMC driver"
> > > + default y
> >
> > This matters less, could stay or could be also ARCH_MDIATEK.
> >
>
> Understood, I will keep this part unchanged in the next version.
> Thank you for your suggestion.
>
> > > + help
> > > + This driver is for the DRAM Controller found in MediaTek
> > > SoCs
> > > + and provides a sysfs interface for reporting the current
> > > DRAM
> > > + data rate.
> > > +
> > > +endif
> >
> > ...
> >
> > > +
> > > +static unsigned int read_reg_field(void __iomem *base, unsigned
> > > int offset, unsigned int mask)
> > > +{
> > > + unsigned int val = readl(base + offset);
> > > + unsigned int shift = __ffs(mask);
> > > +
> > > + return (val & mask) >> shift;
> > > +}
> > > +
> > > +static int mtk_dramc_probe(struct platform_device *pdev)
> >
> > Weird ordering. probe() is one of the last functions. Only other
> > driver
> > struct functions (like remove, suspend/resume) go after, not some
> > regular code.
>
> Understood. I will adjust the order of the probe function in the next
> version.
>
> >
> > > +{
> > > + struct mtk_dramc *dramc;
> > > + const struct mtk_dramc_pdata *pdata;
> > > +
> > > + dramc = devm_kzalloc(&pdev->dev, sizeof(struct mtk_dramc),
> > > GFP_KERNEL);
> > > + if (!dramc)
> > > + return dev_err_probe(&pdev->dev, -ENOMEM, "Failed
> > > to
> > > allocate memory\n");
> > > +
> > > + pdata = of_device_get_match_data(&pdev->dev);
> > > + if (!pdata)
> > > + return dev_err_probe(&pdev->dev, -EINVAL, "No
> > > platform data available\n");
> >
> > Just return. That's impossible condition, so no need for printing
> > errors.
>
> Okay, will return directly in the next version.
>
> >
> > > +
> > > + dramc->pdata = pdata;
> >
> > Why do you need pdata variable in the first place? Make this code
> > simple, not complicated.
>
> Okay, will remove redundant 'pdata' in the next version.
>
> >
> > > +
> > > + dramc->anaphy_base = devm_platform_ioremap_resource(pdev,
> > > 0);
> > > + if (IS_ERR(dramc->anaphy_base))
> > > + return dev_err_probe(&pdev->dev, PTR_ERR(dramc-
> > > > anaphy_base),
> > >
> > > + "Unable to map ANAPHY
> > > base\n");
> > > +
> > > + dramc->ddrphy_base = devm_platform_ioremap_resource(pdev,
> > > 1);
> > > + if (IS_ERR(dramc->ddrphy_base))
> > > + return dev_err_probe(&pdev->dev, PTR_ERR(dramc-
> > > > ddrphy_base),
> > >
> > > + "Unable to map DDRPHY
> > > base\n");
> > > +
> > > + platform_set_drvdata(pdev, dramc);
> > > + return 0;
> > > +}
> > > +
> > > +static unsigned int mtk_fmeter_v1(struct mtk_dramc *dramc)
> > > +{
> > > + const struct mtk_dramc_pdata *pdata = dramc->pdata;
> > > + unsigned int shu_level, pll_sel, offset;
> > > + unsigned int sdmpcw, posdiv, clkdiv, fbksel, sopen,
> > > async_ca,
> > > ser_mode;
> > > + unsigned int prediv_freq, posdiv_freq, vco_freq;
> > > + unsigned int final_rate;
> > > +
> > > + shu_level = read_reg_field(dramc->ddrphy_base, pdata-
> > > > regs[DRAMC_DPHY_DVFS_STA],
> > >
> > > + pdata-
> > > > masks[DRAMC_DPHY_DVFS_SHU_LV]);
> >
> > Don't creat your own wrappers. Use existing FIELD_PREP stuff.
>
> Since the mask is not a constant, using FIELD_GET will result in a
> compilation error. Can I keep using 'read_reg_field' in the next
> version?
>
> >
> > > + pll_sel = read_reg_field(dramc->ddrphy_base, pdata-
> > > > regs[DRAMC_DPHY_DVFS_STA],
> > >
> > > + pdata-
> > > > masks[DRAMC_DPHY_DVFS_PLL_SEL]);
> > >
> > > + offset = pdata->shuffle_offset * shu_level;
> > > +
> > > + sdmpcw = read_reg_field(dramc->anaphy_base,
> > > + ((pll_sel == 0) ?
> > > + pdata->regs[DRAMC_APHY_SHU_PHYPLL2]
> > > :
> > > + pdata-
> > > >regs[DRAMC_APHY_SHU_CLRPLL2])
> > > + offset,
> > > + pdata-
> > > > masks[DRAMC_APHY_PLL2_SDMPCW]);
> > >
> > > + posdiv = read_reg_field(dramc->anaphy_base,
> > > + ((pll_sel == 0) ?
> > > + pdata->regs[DRAMC_APHY_SHU_PHYPLL3]
> > > :
> > > + pdata-
> > > >regs[DRAMC_APHY_SHU_CLRPLL3])
> > > + offset,
> > > + pdata-
> > > > masks[DRAMC_APHY_PLL3_POSDIV]);
> > >
> > > + fbksel = read_reg_field(dramc->anaphy_base, pdata-
> > > > regs[DRAMC_APHY_SHU_PHYPLL4] + offset,
> > >
> > > + pdata-
> > > > masks[DRAMC_APHY_PLL4_FBKSEL]);
> > >
> > > + sopen = read_reg_field(dramc->anaphy_base, pdata-
> > > > regs[DRAMC_APHY_ARPI0] + offset,
> > >
> > > + pdata-
> > > >masks[DRAMC_APHY_ARPI0_SOPEN]);
> > > + async_ca = read_reg_field(dramc->anaphy_base, pdata-
> > > > regs[DRAMC_APHY_CA_ARDLL1] + offset,
> > >
> > > + pdata-
> > > > masks[DRAMC_APHY_ARDLL1_CK_EN]);
> > >
> > > + ser_mode = read_reg_field(dramc->anaphy_base, pdata-
> > > > regs[DRAMC_APHY_B0_TX0] + offset,
> > >
> > > + pdata-
> > > > masks[DRAMC_APHY_B0_TX0_SER_MODE]);
> > >
> > > +
> > > + clkdiv = (ser_mode == 1) ? 1 : 0;
> > > + posdiv &= ~(pdata->posdiv_purify);
> > > +
> > > + prediv_freq = pdata->ref_freq_mhz * (sdmpcw >> pdata-
> > > > prediv);
> > >
> > > + posdiv_freq = (prediv_freq >> posdiv) >> 1;
> > > + vco_freq = posdiv_freq << fbksel;
> > > + final_rate = vco_freq >> clkdiv;
> > > +
> > > + if (sopen == 1 && async_ca == 1)
> > > + final_rate >>= 1;
> > > +
> > > + return final_rate;
> > > +}
> > > +
> > > +/**
> > > + * mtk_dramc_get_data_rate - Calculate the current DRAM data
> > > rate
> > > + * @dev: Device pointer
> > > + *
> > > + * Return: DRAM Data Rate in Mbps or negative number for error
> > > + */
> > > +static unsigned int mtk_dramc_get_data_rate(struct device *dev)
> > > +{
> > > + struct mtk_dramc *dramc = dev_get_drvdata(dev);
> > > +
> > > + if (dramc->pdata->fmeter_version == 1)
> >
> > Drop, it's not possible to have other case.
>
> Thank you for your suggestion.
> In the future, other SoCs might use different fmeter version to
> calculate the DDR data rate. If we remove the fmeter_version now,
> future SoCs that cannot share mtk_fmeter_v1 will still need to add
> the
> fmeter_version variable.
> Therefore, can I keep the current implementation for now.
>
> >
> > > + return mtk_fmeter_v1(dramc);
> > > +
> > > + dev_err(dev, "Frequency meter version %u not supported\n",
> > > dramc->pdata->fmeter_version);
> > > + return -EINVAL;
> > > +}
> > > +
> > > +static ssize_t dram_data_rate_show(struct device *dev,
> > > + struct device_attribute *attr,
> > > char *buf)
> >
> > No ABI doc.
> >
> > Why existing interconnect interface is not suitable here? This
> > should
> > be an interconnect, otherwise what is the point of this driver?
> > What
> > do
> > you exactly configure here?
> >
>
> This driver specifically focuses on displaying the current DDR data
> rate, which is used for the DVFS (Dynamic Voltage and Frequency
> Scaling) feature to verify that DDR frequency adjustments are
> correctly
> executed.
> I will add the ABI documentation for the dram_data_rate attribute as
> follows, is this ok?
>
> What: /sys/devices/platform/soc/XXXXXXXX.memory-
> controller/dram_data_rate
> Date: April 2025
> KernelVersion: 6.14
> Contact: Crystal Guo <crystal.guo@mediatek.com>
> Description:
> Reports the current DRAM data rate in Mbps.
>
> Access: Read
>

Hi Krzysztof,

Sorry to bother you, may I proceed with the next version based on the
content of my previous response regarding your suggestions?

Thanks
Crystal

> > > +{
> > > + return snprintf(buf, PAGE_SIZE, "DRAM data rate = %u\n",
> > > + mtk_dramc_get_data_rate(dev));
> > > +}
> > > +
> > > +static DEVICE_ATTR_RO(dram_data_rate);
> > > +
> > > +static struct attribute *mtk_dramc_attrs[] = {
> > > + &dev_attr_dram_data_rate.attr,
> > > + NULL
> > > +};
> > > +ATTRIBUTE_GROUPS(mtk_dramc);
> > > +
> > > +static const struct mtk_dramc_pdata dramc_pdata_mt8196 = {
> > > + .fmeter_version = 1,
> > > + .ref_freq_mhz = 26,
> > > + .regs = mtk_dramc_regs_mt8196,
> > > + .masks = mtk_dramc_masks_mt8196,
> > > + .posdiv_purify = BIT(2),
> > > + .prediv = 7,
> > > + .shuffle_offset = 0x700,
> > > +};
> > > +
> > > +static const struct of_device_id mtk_dramc_of_ids[] = {
> > > + { .compatible = "mediatek,mt8196-dramc", .data =
> > > &dramc_pdata_mt8196 },
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, mtk_dramc_of_ids);
> > > +
> > > +static struct platform_driver mtk_dramc_driver = {
> > > + .probe = mtk_dramc_probe,
> > > + .driver = {
> > > + .name = "mtk-dramc",
> > > + .of_match_table = mtk_dramc_of_ids,
> > > + .dev_groups = mtk_dramc_groups,
> > > + },
> > > +};
> > > +module_platform_driver(mtk_dramc_driver);
> > > +
> > > +MODULE_AUTHOR("Crystal Guo <crystal.guo@mediatek.com>");
> > > +MODULE_DESCRIPTION("MediaTek DRAM Controller Driver");
> > > +MODULE_LICENSE("GPL");
> > > --
> > > 2.18.0
> > >