[PATCH v1 5/5] imx: mach: imx8: fdt: set correct frequencies for the industrial SoC

Stefan Eichenberger eichest at gmail.com
Wed Dec 11 13:18:55 CET 2024


From: Stefan Eichenberger <stefan.eichenberger at toradex.com>

Set correct CPU and GPU frequencies for the industrial i.MX8 SoC
variant.

Ensure that the CPU and GPU frequencies are properly configured for the
industrial variant of the SoC. According to the "i.MX 8QuadMax
Industrial Applications Processors" datasheet, the frequency limits for
this variant are as follows:
- Cortex-A72: 1.296 GHz
- Cortex-A53: 1.104 GHz
- GPU core: 625 MHz
- GPU shader: 625 MHz

The CPU clock is enforced by the System Controller Firmware (SCFW), but
the cpufreq driver is unaware of this enforcement. By removing
unsupported frequencies from the operating points, we ensure that the
cpufreq driver aligns correctly with the SCFW's settings.

The GPU frequency, on the other hand, is not enforced by the SCFW. As a
result, the GPU could potentially be overclocked. To prevent this, we
set the correct clock frequency and update the operating points
accordingly, ensuring compliance with the datasheet specifications.

Signed-off-by: Stefan Eichenberger <stefan.eichenberger at toradex.com>
---
 arch/arm/mach-imx/imx8/fdt.c | 132 +++++++++++++++++++++++++++++++++++
 1 file changed, 132 insertions(+)

diff --git a/arch/arm/mach-imx/imx8/fdt.c b/arch/arm/mach-imx/imx8/fdt.c
index 6d0585f5cc6..ce78c8ce919 100644
--- a/arch/arm/mach-imx/imx8/fdt.c
+++ b/arch/arm/mach-imx/imx8/fdt.c
@@ -11,6 +11,8 @@
 #include <fdt_support.h>
 #include <linux/libfdt.h>
 #include <linux/printk.h>
+#include <cpu.h>
+#include <dm.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -279,6 +281,134 @@ static int ft_add_optee_node(void *fdt, struct bd_info *bd)
 	return 0;
 }
 
+static int delete_node(void *blob, const char *node)
+{
+	int nodeoffset;
+	int err;
+
+	nodeoffset = fdt_path_offset(blob, node);
+	if (nodeoffset < 0)
+		return -ENXIO;
+
+	err = fdt_del_node(blob, nodeoffset);
+	if (err)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int change_property(void *blob, const char *node, const char *property,
+			   const void *value, int len)
+{
+	int nodeoffset;
+	int err;
+
+	nodeoffset = fdt_path_offset(blob, node);
+	if (nodeoffset < 0)
+		return -ENXIO;
+
+	err = fdt_setprop(blob, nodeoffset, property, value, len);
+	if (err)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void update_fdt_gpu_industrial_frequencies(void *blob)
+{
+	u32 gpu_opp_table[6];
+	u32 gpu_assigned_clocks[2];
+	int err;
+
+	gpu_opp_table[0] = cpu_to_fdt32(625000); /* Normal Core Clock */
+	gpu_opp_table[1] = cpu_to_fdt32(0);
+	gpu_opp_table[2] = cpu_to_fdt32(625000); /* Normal Shader Clock */
+	gpu_opp_table[3] = cpu_to_fdt32(0);
+	gpu_opp_table[4] = cpu_to_fdt32(400000); /* Low Shader and Core Clock */
+	gpu_opp_table[5] = cpu_to_fdt32(0);
+
+	gpu_assigned_clocks[0] = cpu_to_fdt32(625000000); /* Core Clock */
+	gpu_assigned_clocks[1] = cpu_to_fdt32(625000000); /* Shader Clock */
+
+	err = change_property(blob, "/bus at 53100000/gpu at 53100000",
+			      "assigned-clock-rates", gpu_assigned_clocks,
+			      sizeof(gpu_assigned_clocks));
+	if (err && err != ENXIO)
+		printf("Failed to set assigned-clock-rates for GPU0: %s\n",
+		       fdt_strerror(err));
+
+	err = change_property(blob, "/bus at 54100000/gpu at 54100000",
+			      "assigned-clock-rates", gpu_assigned_clocks,
+			      sizeof(gpu_assigned_clocks));
+	if (err && err != ENXIO)
+		printf("Failed to set assigned-clock-rates for GPU1: %s\n",
+		       fdt_strerror(err));
+
+	err = change_property(blob, "/bus at 54100000/imx8_gpu1_ss at 80000000",
+			      "operating-points", &gpu_opp_table,
+			      sizeof(gpu_opp_table));
+	if (err && err != ENXIO)
+		printf("Failed to set operating-points for GPU: %s\n",
+		       fdt_strerror(err));
+}
+
+static void update_fdt_cpu_industrial_frequencies(void *blob)
+{
+	int err;
+
+	err = delete_node(blob, "/opp-table-0/opp-1200000000");
+	if (err && err != -ENXIO)
+		printf("Failed to delete 1.2 GHz node on A53: %s\n",
+		       fdt_strerror(err));
+
+	err = delete_node(blob, "/opp-table-1/opp-1596000000");
+	if (err && err != -ENXIO)
+		printf("Failed to delete 1.596 GHz node on A72: %s\n",
+		       fdt_strerror(err));
+}
+
+static void update_fdt_frequencies(void *blob)
+{
+	struct cpu_info cpu;
+	struct udevice *dev;
+	int err;
+
+	uclass_first_device(UCLASS_CPU, &dev);
+
+	err = cpu_get_info(dev, &cpu);
+	if (err) {
+		printf("Failed to get CPU info\n");
+		return;
+	}
+
+	/*
+	 * Differentiate between the automotive and industrial variants of the
+	 * i.MX8. The difference of these two CPUs is the maximum frequencies
+	 * for the CPU and GPU.
+	 * Core			Automotive [max. MHz]	Industrial [max. MHz]
+	 * A53			1200			1104
+	 * A72			1596			1296
+	 * GPU Core		800			625
+	 * GPU Shader		1000			625
+	 *
+	 * While the SCFW enforces these limits for the CPU, the OS cpufreq
+	 * driver remains unaware, causing a mismatch between reported and
+	 * actual frequencies. This is resolved by removing the unsupprted
+	 * frequencies from the device tree.
+	 *
+	 * The GPU frequencies are not enforced by the SCFW, therefore without
+	 * updating the device tree we overclock the GPU.
+	 *
+	 * Using the cpu_freq variable is the only know way to differentiate
+	 * between the automotive and industrial variants of the i.MX8.
+	 */
+	if (cpu.cpu_freq != 1104000000)
+		return;
+
+	update_fdt_cpu_industrial_frequencies(blob);
+	update_fdt_gpu_industrial_frequencies(blob);
+}
+
 int ft_system_setup(void *blob, struct bd_info *bd)
 {
 	int ret;
@@ -294,6 +424,8 @@ int ft_system_setup(void *blob, struct bd_info *bd)
 
 	update_fdt_with_owned_resources(blob);
 
+	update_fdt_frequencies(blob);
+
 	if (is_imx8qm()) {
 		ret = config_smmu_fdt(blob);
 		if (ret)
-- 
2.45.2



More information about the U-Boot mailing list