feat(arm_fpga): query PL011 to learn system frequency

The Arm FPGAs run in mostly one clock domain, which is used for the CPU
cores, the generic timer, and also the UART baudrate base clock. This
single clock can have different rates, to compensate for different IP
complexity. So far most images used 10 MHz, but different rates start to
appear.

To avoid patching both the arch timer frequency and UART baud base fixed
clock in the DTB manually, we would like to set the clock rate
automatically. Fortunately the SCP firmware has the actual clock rate
hard coded, and already programs the PL011 UART baud divider register
with the correct value to achieve a 38400 bps baudrate.

So read the two PL011 baudrate divider values and re-calculate the
original base clock from there, to use as the arch timer frequency. If
the arch timer DT node contains a clock-frequency property, we use that
instead, to support overriding and disabling this autodetection.

Change-Id: I9857fbb418deb4644aeb2816f1102796f9bfd3bb
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
diff --git a/plat/arm/board/arm_fpga/fpga_bl31_setup.c b/plat/arm/board/arm_fpga/fpga_bl31_setup.c
index 86f00ab..80a7014 100644
--- a/plat/arm/board/arm_fpga/fpga_bl31_setup.c
+++ b/plat/arm/board/arm_fpga/fpga_bl31_setup.c
@@ -13,6 +13,7 @@
 #include <drivers/delay_timer.h>
 #include <drivers/generic_delay_timer.h>
 #include <lib/extensions/spe.h>
+#include <lib/mmio.h>
 #include <libfdt.h>
 
 #include "fpga_private.h"
@@ -118,18 +119,94 @@
 	}
 }
 
-unsigned int plat_get_syscnt_freq2(void)
+/*
+ * Even though we sell the FPGA UART as an SBSA variant, it is actually
+ * a full fledged PL011. So the baudrate divider registers exist.
+ */
+#ifndef UARTIBRD
+#define UARTIBRD	0x024
+#define UARTFBRD	0x028
+#endif
+
+/* Round an integer to the closest multiple of a value. */
+static unsigned int round_multiple(unsigned int x, unsigned int multiple)
+{
+	if (multiple < 2) {
+		return x;
+	}
+
+	return ((x + (multiple / 2 - 1)) / multiple) * multiple;
+}
+
+#define PL011_FRAC_SHIFT	6
+#define FPGA_DEFAULT_BAUDRATE	38400
+#define PL011_OVERSAMPLING	16
+static unsigned int pl011_freq_from_divider(unsigned int divider)
+{
+	unsigned int freq;
+
+	freq = divider * FPGA_DEFAULT_BAUDRATE * PL011_OVERSAMPLING;
+
+	return freq >> PL011_FRAC_SHIFT;
+}
+
+/*
+ * The FPGAs run most peripherals from one main clock, among them the CPUs,
+ * the arch timer, and the UART baud base clock.
+ * The SCP knows this frequency and programs the UART clock divider for a
+ * 38400 bps baudrate. Recalculate the base input clock from there.
+ */
+static unsigned int fpga_get_system_frequency(void)
 {
 	const void *fdt = (void *)(uintptr_t)FPGA_PRELOADED_DTB_BASE;
-	int node;
+	int node, err;
 
+	/*
+	 * If the arch timer DT node has an explicit clock-frequency property
+	 * set, use that, to allow people overriding auto-detection.
+	 */
 	node = fdt_node_offset_by_compatible(fdt, 0, "arm,armv8-timer");
-	if (node < 0) {
-		return FPGA_DEFAULT_TIMER_FREQUENCY;
+	if (node >= 0) {
+		uint32_t freq;
+
+		err = fdt_read_uint32(fdt, node, "clock-frequency", &freq);
+		if (err >= 0) {
+			return freq;
+		}
+	}
+
+	node = fdt_node_offset_by_compatible(fdt, 0, "arm,pl011");
+	if (node >= 0) {
+		uintptr_t pl011_base;
+		unsigned int divider;
+
+		err = fdt_get_reg_props_by_index(fdt, node, 0,
+						 &pl011_base, NULL);
+		if (err >= 0) {
+			divider = mmio_read_32(pl011_base + UARTIBRD);
+			divider <<= PL011_FRAC_SHIFT;
+			divider += mmio_read_32(pl011_base + UARTFBRD);
+
+			/*
+			 * The result won't be exact, due to rounding errors,
+			 * but the input frequency was a multiple of 250 KHz.
+			 */
+			return round_multiple(pl011_freq_from_divider(divider),
+					      250000);
+		} else {
+			WARN("Cannot read PL011 MMIO base\n");
+		}
+	} else {
+		WARN("No PL011 DT node\n");
 	}
 
+	/* No PL011 DT node or calculation failed. */
+	return FPGA_DEFAULT_TIMER_FREQUENCY;
+}
+
-	return fdt_read_uint32_default(fdt, node, "clock-frequency",
-				       FPGA_DEFAULT_TIMER_FREQUENCY);
+unsigned int plat_get_syscnt_freq2(void)
+{
+	return fpga_get_system_frequency();
 }
 
 #define CMDLINE_SIGNATURE	"CMD:"