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"
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
+ 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
+ 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
diff --git a/drivers/memory/mediatek/Makefile b/drivers/memory/mediatek/Makefile
new file mode 100644
index 000000000000..a1395fc55b41
--- /dev/null
+++ b/drivers/memory/mediatek/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_MTK_DRAMC) += mtk-dramc.o
diff --git a/drivers/memory/mediatek/mtk-dramc.c b/drivers/memory/mediatek/mtk-dramc.c
new file mode 100644
index 000000000000..d54c8e00d4ee
--- /dev/null
+++ b/drivers/memory/mediatek/mtk-dramc.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 MediaTek Inc.
+ */
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+
+struct mtk_dramc_pdata {
+ u8 fmeter_version;
+ u8 ref_freq_mhz;
+ const u16 *regs;
+ const u32 *masks;
+ u32 posdiv_purify;
+ u8 prediv;
+ u16 shuffle_offset;
+};
+
+struct mtk_dramc {
+ void __iomem *anaphy_base;
+ void __iomem *ddrphy_base;
+ const struct mtk_dramc_pdata *pdata;
+};
+
+enum mtk_dramc_reg_index {
+ DRAMC_DPHY_DVFS_STA,
+ DRAMC_APHY_SHU_PHYPLL2,
+ DRAMC_APHY_SHU_CLRPLL2,
+ DRAMC_APHY_SHU_PHYPLL3,
+ DRAMC_APHY_SHU_CLRPLL3,
+ DRAMC_APHY_SHU_PHYPLL4,
+ DRAMC_APHY_ARPI0,
+ DRAMC_APHY_CA_ARDLL1,
+ DRAMC_APHY_B0_TX0,
+};
+
+enum mtk_dramc_mask_index {
+ DRAMC_DPHY_DVFS_SHU_LV,
+ DRAMC_DPHY_DVFS_PLL_SEL,
+ DRAMC_APHY_PLL2_SDMPCW,
+ DRAMC_APHY_PLL3_POSDIV,
+ DRAMC_APHY_PLL4_FBKSEL,
+ DRAMC_APHY_ARPI0_SOPEN,
+ DRAMC_APHY_ARDLL1_CK_EN,
+ DRAMC_APHY_B0_TX0_SER_MODE,
+};
+
+static const u16 mtk_dramc_regs_mt8196[] = {
+ [DRAMC_DPHY_DVFS_STA] = 0xe98,
+ [DRAMC_APHY_SHU_PHYPLL2] = 0x908,
+ [DRAMC_APHY_SHU_CLRPLL2] = 0x928,
+ [DRAMC_APHY_SHU_PHYPLL3] = 0x90c,
+ [DRAMC_APHY_SHU_CLRPLL3] = 0x92c,
+ [DRAMC_APHY_SHU_PHYPLL4] = 0x910,
+ [DRAMC_APHY_ARPI0] = 0x0d94,
+ [DRAMC_APHY_CA_ARDLL1] = 0x0d08,
+ [DRAMC_APHY_B0_TX0] = 0x0dc4,
+};
+
+static const u32 mtk_dramc_masks_mt8196[] = {
+ [DRAMC_DPHY_DVFS_SHU_LV] = GENMASK(15, 14),
+ [DRAMC_DPHY_DVFS_PLL_SEL] = GENMASK(25, 25),
+ [DRAMC_APHY_PLL2_SDMPCW] = GENMASK(18, 3),
+ [DRAMC_APHY_PLL3_POSDIV] = GENMASK(13, 11),
+ [DRAMC_APHY_PLL4_FBKSEL] = GENMASK(6, 6),
+ [DRAMC_APHY_ARPI0_SOPEN] = GENMASK(26, 26),
+ [DRAMC_APHY_ARDLL1_CK_EN] = GENMASK(0, 0),
+ [DRAMC_APHY_B0_TX0_SER_MODE] = GENMASK(4, 3),
+};
+
+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)
+{
+ 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");
+
+ dramc->pdata = pdata;
+
+ 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]);
+ 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)
+ 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)
+{
+ 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
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://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters
memory: mediatek:
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.
>
> 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.
> + 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.
> + 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.
> +{
> + 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.
> +
> + dramc->pdata = pdata;
Why do you need pdata variable in the first place? Make this code
simple, not complicated.
> +
> + 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.
> + 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.
> + 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?
> +{
> + 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
>
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
>
> > +{
> > + 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
> >
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
> > >
Il 03/04/25 08:48, Crystal Guo ha scritto: > Add MediaTek DRAMC driver to provide an interface that can > obtain current DDR data rate. > > Signed-off-by: Crystal Guo <crystal.guo@mediatek.com> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
© 2016 - 2026 Red Hat, Inc.