// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2023, Arm Ltd.
 *
 * Use the (optional) ARMv8.5 RNDR register to provide random numbers to
 * U-Boot's UCLASS_RNG users.
 * Detection is done at runtime using the CPU ID registers.
 */

#define LOG_CATEGORY UCLASS_RNG

#include <common.h>
#include <dm.h>
#include <linux/kernel.h>
#include <rng.h>
#include <asm/system.h>

#define DRIVER_NAME	"arm-rndr"

static bool cpu_has_rndr(void)
{
	uint64_t reg;

	__asm__ volatile("mrs %0, ID_AA64ISAR0_EL1\n" : "=r" (reg));
	return !!(reg & ID_AA64ISAR0_EL1_RNDR);
}

/*
 * The system register name is RNDR, but this isn't widely known among older
 * toolchains, and also triggers errors because of it being an architecture
 * extension. Since we check the availability of the register before, it's
 * fine to use here, though.
 */
#define RNDR	"S3_3_C2_C4_0"

static uint64_t read_rndr(void)
{
	uint64_t reg;

	__asm__ volatile("mrs %0, " RNDR "\n" : "=r" (reg));

	return reg;
}

static int arm_rndr_read(struct udevice *dev, void *data, size_t len)
{
	uint64_t random;

	while (len) {
		int tocopy = min(sizeof(uint64_t), len);

		random = read_rndr();
		memcpy(data, &random, tocopy);
		len -= tocopy;
		data += tocopy;
	}

	return 0;
}

static const struct dm_rng_ops arm_rndr_ops = {
	.read = arm_rndr_read,
};

static int arm_rndr_probe(struct udevice *dev)
{
	if (!cpu_has_rndr())
		return -ENODEV;

	return 0;
}

U_BOOT_DRIVER(arm_rndr) = {
	.name = DRIVER_NAME,
	.id = UCLASS_RNG,
	.ops = &arm_rndr_ops,
	.probe = arm_rndr_probe,
};

U_BOOT_DRVINFO(cpu_arm_rndr) = {
	.name = DRIVER_NAME,
};
