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

Crystal Guo (郭晶) Crystal.Guo at mediatek.com
Mon May 12 00:36:53 PDT 2025


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 at 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 at 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 at mediatek.com>");
> > > +MODULE_DESCRIPTION("MediaTek DRAM Controller Driver");
> > > +MODULE_LICENSE("GPL");
> > > --
> > > 2.18.0
> > > 


More information about the Linux-mediatek mailing list