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>
This commit is contained in:
Andre Przywara 2021-09-01 11:59:08 +01:00
parent 52b8f4465e
commit d850169c9c
2 changed files with 86 additions and 10 deletions

View File

@ -40,7 +40,6 @@
timer {
compatible = "arm,armv8-timer";
clock-frequency = <10000000>;
interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,

View File

@ -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 @@ entry_point_info_t *bl31_plat_get_next_image_ep_info(uint32_t type)
}
}
unsigned int plat_get_syscnt_freq2(void)
{
const void *fdt = (void *)(uintptr_t)FPGA_PRELOADED_DTB_BASE;
int node;
/*
* 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
node = fdt_node_offset_by_compatible(fdt, 0, "arm,armv8-timer");
if (node < 0) {
return FPGA_DEFAULT_TIMER_FREQUENCY;
/* 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 fdt_read_uint32_default(fdt, node, "clock-frequency",
FPGA_DEFAULT_TIMER_FREQUENCY);
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, 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) {
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;
}
unsigned int plat_get_syscnt_freq2(void)
{
return fpga_get_system_frequency();
}
#define CMDLINE_SIGNATURE "CMD:"