aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/dts/exynos850-e850-96-u-boot.dtsi11
-rw-r--r--arch/arm/mach-exynos/Kconfig2
-rw-r--r--board/samsung/e850-96/Makefile4
-rw-r--r--board/samsung/e850-96/e850-96.c6
-rw-r--r--board/samsung/e850-96/e850-96.env26
-rw-r--r--board/samsung/e850-96/fw.c131
-rw-r--r--board/samsung/e850-96/fw.h12
-rw-r--r--board/samsung/odroid/Makefile2
-rw-r--r--board/samsung/smdk5420/Kconfig2
-rw-r--r--configs/e850-96_defconfig1
-rw-r--r--drivers/clk/exynos/clk-exynos850.c10
-rw-r--r--drivers/net/essedma.c4
-rw-r--r--drivers/net/essedma.h2
-rw-r--r--drivers/rng/Kconfig13
-rw-r--r--drivers/rng/Makefile1
-rw-r--r--drivers/rng/exynos-trng.c291
16 files changed, 509 insertions, 9 deletions
diff --git a/arch/arm/dts/exynos850-e850-96-u-boot.dtsi b/arch/arm/dts/exynos850-e850-96-u-boot.dtsi
index 6d7148f7264..3aa5d8bb10d 100644
--- a/arch/arm/dts/exynos850-e850-96-u-boot.dtsi
+++ b/arch/arm/dts/exynos850-e850-96-u-boot.dtsi
@@ -3,6 +3,17 @@
* Copyright (c) 2023 Linaro Ltd.
*/
+&soc {
+ /* TODO: Remove this node once it appears in upstream dts */
+ trng: rng@12081400 {
+ compatible = "samsung,exynos850-trng";
+ reg = <0x12081400 0x100>;
+ clocks = <&cmu_core CLK_GOUT_SSS_ACLK>,
+ <&cmu_core CLK_GOUT_SSS_PCLK>;
+ clock-names = "secss", "pclk";
+ };
+};
+
&pmu_system_controller {
bootph-all;
samsung,uart-debug-1;
diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig
index cad8bb044cf..3fee5a4299b 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -250,6 +250,8 @@ config TARGET_E850_96
select PINCTRL
select PINCTRL_EXYNOS850
imply OF_UPSTREAM
+ imply DM_RNG
+ imply RNG_EXYNOS
endchoice
endif
diff --git a/board/samsung/e850-96/Makefile b/board/samsung/e850-96/Makefile
index 301c2233711..71d46ea3d2b 100644
--- a/board/samsung/e850-96/Makefile
+++ b/board/samsung/e850-96/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
#
-# Copyright (C) 2020, Linaro Limited
+# Copyright (C) 2024, Linaro Limited
# Sam Protsenko <semen.protsenko@linaro.org>
-obj-y := e850-96.o
+obj-y := e850-96.o fw.o
diff --git a/board/samsung/e850-96/e850-96.c b/board/samsung/e850-96/e850-96.c
index a00d81b5d4c..c5cef6f19d2 100644
--- a/board/samsung/e850-96/e850-96.c
+++ b/board/samsung/e850-96/e850-96.c
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2020, Linaro Limited
- * Sam Protsenko <semen.protsenko@linaro.org>
+ * Copyright (c) 2024, Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
*/
#include <init.h>
+#include "fw.h"
int dram_init(void)
{
@@ -18,5 +19,6 @@ int dram_init_banksize(void)
int board_init(void)
{
+ load_ldfw();
return 0;
}
diff --git a/board/samsung/e850-96/e850-96.env b/board/samsung/e850-96/e850-96.env
new file mode 100644
index 00000000000..f36f90be950
--- /dev/null
+++ b/board/samsung/e850-96/e850-96.env
@@ -0,0 +1,26 @@
+partitions=
+ uuid_disk=${uuid_gpt_disk};
+ name=efs,start=512K,size=20M,uuid=${uuid_gpt_efs};
+ name=env,size=16K,uuid=${uuid_gpt_env};
+ name=kernel,size=30M,uuid=${uuid_gpt_kernel};
+ name=ramdisk,size=26M,uuid=${uuid_gpt_ramdisk};
+ name=dtbo,size=1M,uuid=${uuid_gpt_dtbo};
+ name=ldfw,size=4016K,uuid=${uuid_gpt_ldfw};
+ name=keystorage,size=8K,uuid=${uuid_gpt_keystorage};
+ name=tzsw,size=1M,uuid=${uuid_gpt_tzsw};
+ name=harx,size=2M,uuid=${uuid_gpt_harx};
+ name=harx_rkp,size=2M,uuid=${uuid_gpt_harx_rkp};
+ name=logo,size=40M,uuid=${uuid_gpt_logo};
+ name=super,size=3600M,uuid=${uuid_gpt_super};
+ name=cache,size=300M,uuid=${uuid_gpt_cache};
+ name=modem,size=100M,uuid=${uuid_gpt_modem};
+ name=boot,size=100M,uuid=${uuid_gpt_boot};
+ name=persist,size=30M,uuid=${uuid_gpt_persist};
+ name=recovery,size=40M,uuid=${uuid_gpt_recovery};
+ name=misc,size=40M,uuid=${uuid_gpt_misc};
+ name=mnv,size=20M,uuid=${uuid_gpt_mnv};
+ name=frp,size=512K,uuid=${uuid_gpt_frp};
+ name=vbmeta,size=64K,uuid=${uuid_gpt_vbmeta};
+ name=metadata,size=16M,uuid=${uuid_gpt_metadata};
+ name=dtb,size=1M,uuid=${uuid_gpt_dtb};
+ name=userdata,size=-,uuid=${uuid_gpt_userdata}
diff --git a/board/samsung/e850-96/fw.c b/board/samsung/e850-96/fw.c
new file mode 100644
index 00000000000..82a0b224c67
--- /dev/null
+++ b/board/samsung/e850-96/fw.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2024 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Firmware loading code.
+ */
+
+#include <part.h>
+#include <linux/arm-smccc.h>
+#include "fw.h"
+
+#define EMMC_IFACE "mmc"
+#define EMMC_DEV_NUM 0
+
+/* LDFW constants */
+#define LDFW_PART_NAME "ldfw"
+#define LDFW_NWD_ADDR 0x88000000
+#define LDFW_MAGIC 0x10adab1e
+#define SMC_CMD_LOAD_LDFW -0x500
+#define SDM_HW_RESET_STATUS 0x1230
+#define SDM_SW_RESET_STATUS 0x1231
+#define SB_ERROR_PREFIX 0xfdaa0000
+
+struct ldfw_header {
+ u32 magic;
+ u32 size;
+ u32 init_entry;
+ u32 entry_point;
+ u32 suspend_entry;
+ u32 resume_entry;
+ u32 start_smc_id;
+ u32 version;
+ u32 set_runtime_entry;
+ u32 reserved[3];
+ char fw_name[16];
+};
+
+static int read_fw(const char *part_name, void *buf)
+{
+ struct blk_desc *blk_desc;
+ struct disk_partition part;
+ unsigned long cnt;
+ int part_num;
+
+ blk_desc = blk_get_dev(EMMC_IFACE, EMMC_DEV_NUM);
+ if (!blk_desc) {
+ debug("%s: Can't get eMMC device\n", __func__);
+ return -ENODEV;
+ }
+
+ part_num = part_get_info_by_name(blk_desc, part_name, &part);
+ if (part_num < 0) {
+ debug("%s: Can't get LDWF partition\n", __func__);
+ return -ENOENT;
+ }
+
+ cnt = blk_dread(blk_desc, part.start, part.size, buf);
+ if (cnt != part.size) {
+ debug("%s: Can't read LDFW partition\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int load_ldfw(void)
+{
+ const phys_addr_t addr = (phys_addr_t)LDFW_NWD_ADDR;
+ struct ldfw_header *hdr;
+ struct arm_smccc_res res;
+ void *buf = (void *)addr;
+ u64 size = 0;
+ int err, i;
+
+ /* Load LDFW from the block device partition into RAM buffer */
+ err = read_fw(LDFW_PART_NAME, buf);
+ if (err)
+ return err;
+
+ /* Validate LDFW by magic number in its header */
+ hdr = buf;
+ if (hdr->magic != LDFW_MAGIC) {
+ debug("%s: Wrong LDFW magic; is LDFW flashed?\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Calculate actual total size of all LDFW blobs */
+ for (i = 0; hdr->magic == LDFW_MAGIC; ++i) {
+#ifdef DEBUG
+ char name[17] = { 0 };
+
+ strncpy(name, hdr->fw_name, 16);
+ debug("%s: ldfw #%d: version = 0x%x, name = %s\n", __func__, i,
+ hdr->version, name);
+#endif
+
+ size += (u64)hdr->size;
+ hdr = (struct ldfw_header *)((u64)hdr + (u64)hdr->size);
+ }
+ debug("%s: The whole size of all LDFWs: 0x%llx\n", __func__, size);
+
+ /* Load LDFW firmware to SWD (Secure World) memory via EL3 monitor */
+ arm_smccc_smc(SMC_CMD_LOAD_LDFW, addr, size, 0, 0, 0, 0, 0, &res);
+ err = (int)res.a0;
+ if (err == -1 || err == SDM_HW_RESET_STATUS) {
+ debug("%s: Can't load LDFW in dump_gpr state\n", __func__);
+ return -EIO;
+ } else if (err == SDM_SW_RESET_STATUS) {
+ debug("%s: Can't load LDFW in kernel panic (SW RESET) state\n",
+ __func__);
+ return -EIO;
+ } else if (err < 0 && (err & 0xffff0000) == SB_ERROR_PREFIX) {
+ debug("%s: LDFW signature is corrupted! ret=0x%x\n", __func__,
+ (u32)err);
+ return -EIO;
+ } else if (err == 0) {
+ debug("%s: No LDFW is inited\n", __func__);
+ return -EIO;
+ }
+
+#ifdef DEBUG
+ u32 tried = res.a0 & 0xffff;
+ u32 failed = (res.a0 >> 16) & 0xffff;
+
+ debug("%s: %d/%d LDFWs have been loaded successfully\n", __func__,
+ tried - failed, tried);
+#endif
+
+ return 0;
+}
diff --git a/board/samsung/e850-96/fw.h b/board/samsung/e850-96/fw.h
new file mode 100644
index 00000000000..472664e4ed2
--- /dev/null
+++ b/board/samsung/e850-96/fw.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2024 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ */
+
+#ifndef __E850_96_FW_H
+#define __E850_96_FW_H
+
+int load_ldfw(void);
+
+#endif /* __E850_96_FW_H */
diff --git a/board/samsung/odroid/Makefile b/board/samsung/odroid/Makefile
index 5bf48313de3..615c99f5019 100644
--- a/board/samsung/odroid/Makefile
+++ b/board/samsung/odroid/Makefile
@@ -3,4 +3,4 @@
# Copyright (c) 2014 Samsung Electronics Co., Ltd. All rights reserved.
# Przemyslaw Marczak <p.marczak@samsung.com>
-obj-y := odroid.o
+obj-$(CONFIG_TARGET_ODROID) := odroid.o
diff --git a/board/samsung/smdk5420/Kconfig b/board/samsung/smdk5420/Kconfig
index a9d62fffa55..37d6bbbed1b 100644
--- a/board/samsung/smdk5420/Kconfig
+++ b/board/samsung/smdk5420/Kconfig
@@ -1,7 +1,7 @@
if TARGET_ODROID_XU3
config SYS_BOARD
- default "smdk5420"
+ default "odroid"
config SYS_VENDOR
default "samsung"
diff --git a/configs/e850-96_defconfig b/configs/e850-96_defconfig
index 38b9968c167..2949da24267 100644
--- a/configs/e850-96_defconfig
+++ b/configs/e850-96_defconfig
@@ -11,6 +11,7 @@ CONFIG_DEFAULT_DEVICE_TREE="exynos/exynos850-e850-96"
CONFIG_SYS_LOAD_ADDR=0x80000000
# CONFIG_AUTOBOOT is not set
# CONFIG_DISPLAY_CPUINFO is not set
+CONFIG_CMD_RNG=y
# CONFIG_NET is not set
CONFIG_CLK_EXYNOS850=y
# CONFIG_MMC is not set
diff --git a/drivers/clk/exynos/clk-exynos850.c b/drivers/clk/exynos/clk-exynos850.c
index 0c09ba02de4..8cbc626f31e 100644
--- a/drivers/clk/exynos/clk-exynos850.c
+++ b/drivers/clk/exynos/clk-exynos850.c
@@ -323,14 +323,18 @@ U_BOOT_DRIVER(exynos850_cmu_peri) = {
/* Register Offset definitions for CMU_CORE (0x12000000) */
#define PLL_CON0_MUX_CLKCMU_CORE_BUS_USER 0x0600
#define PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER 0x0620
+#define PLL_CON0_MUX_CLKCMU_CORE_SSS_USER 0x0630
#define CLK_CON_DIV_DIV_CLK_CORE_BUSP 0x1800
#define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_I_ACLK 0x20e8
#define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN 0x20ec
+#define CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK 0x2128
+#define CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK 0x212c
/* List of parent clocks for Muxes in CMU_CORE */
PNAME(mout_core_bus_user_p) = { "clock-oscclk", "dout_core_bus" };
PNAME(mout_core_mmc_embd_user_p) = { "clock-oscclk",
"dout_core_mmc_embd" };
+PNAME(mout_core_sss_user_p) = { "clock-oscclk", "dout_core_sss" };
static const struct samsung_mux_clock core_mux_clks[] = {
MUX(CLK_MOUT_CORE_BUS_USER, "mout_core_bus_user", mout_core_bus_user_p,
@@ -338,6 +342,8 @@ static const struct samsung_mux_clock core_mux_clks[] = {
MUX_F(CLK_MOUT_CORE_MMC_EMBD_USER, "mout_core_mmc_embd_user",
mout_core_mmc_embd_user_p, PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER,
4, 1, CLK_SET_RATE_PARENT, 0),
+ MUX(CLK_MOUT_CORE_SSS_USER, "mout_core_sss_user", mout_core_sss_user_p,
+ PLL_CON0_MUX_CLKCMU_CORE_SSS_USER, 4, 1),
};
static const struct samsung_div_clock core_div_clks[] = {
@@ -351,6 +357,10 @@ static const struct samsung_gate_clock core_gate_clks[] = {
GATE(CLK_GOUT_MMC_EMBD_SDCLKIN, "gout_mmc_embd_sdclkin",
"mout_core_mmc_embd_user", CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN,
21, CLK_SET_RATE_PARENT, 0),
+ GATE(CLK_GOUT_SSS_ACLK, "gout_sss_aclk", "mout_core_sss_user",
+ CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK, 21, 0, 0),
+ GATE(CLK_GOUT_SSS_PCLK, "gout_sss_pclk", "dout_core_busp",
+ CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK, 21, 0, 0),
};
static const struct samsung_clk_group core_cmu_clks[] = {
diff --git a/drivers/net/essedma.c b/drivers/net/essedma.c
index 0de363806ff..fccc5f5a440 100644
--- a/drivers/net/essedma.c
+++ b/drivers/net/essedma.c
@@ -163,7 +163,7 @@ void qca8075_ess_reset(struct udevice *dev)
#define PSGMII_ST_TRAFFIC_TIMEOUT \
DIV_ROUND_UP(PSGMII_ST_TRAFFIC_TIMEOUT_NS, 1000000)
-static bool psgmii_self_test_repeat = false;
+static bool psgmii_self_test_repeat;
static void psgmii_st_phy_power_down(struct phy_device *phydev)
{
@@ -267,7 +267,7 @@ static inline bool psgmii_st_phy_link_is_up(struct phy_device *phydev)
static bool psgmii_st_phy_wait(struct ess_switch *esw, u32 mask,
int retries, int delay,
- bool (*check)(struct phy_device*))
+ bool (*check)(struct phy_device *))
{
int i;
diff --git a/drivers/net/essedma.h b/drivers/net/essedma.h
index e0b5c968ca8..067cb442fb4 100644
--- a/drivers/net/essedma.h
+++ b/drivers/net/essedma.h
@@ -192,7 +192,7 @@ struct edma_rrd {
/* Receive Free Descriptor */
struct edma_rfd {
- u32 buffer_addr; /* buffer address */
+ u32 buffer_addr; /* buffer address */
};
#endif /* _ESSEDMA_ETH_H */
diff --git a/drivers/rng/Kconfig b/drivers/rng/Kconfig
index 5758ae192a6..b35d8c66b9c 100644
--- a/drivers/rng/Kconfig
+++ b/drivers/rng/Kconfig
@@ -120,4 +120,17 @@ config RNG_TURRIS_RWTM
on other Armada-3700 devices (like EspressoBin) if Secure
Firmware from CZ.NIC is used.
+config RNG_EXYNOS
+ bool "Samsung Exynos True Random Number Generator support"
+ depends on DM_RNG
+ help
+ Enable support for True Random Number Generator (TRNG) available on
+ Exynos SoCs.
+
+ On some chips (like Exynos850) TRNG registers are protected with TZPC
+ (TrustZone Protection Control). For such chips the driver provides an
+ implementation based on SMC calls to EL3 monitor program. In that
+ case the LDFW (Loadable Firmware) has to be loaded first, as it
+ actually implements TRNG SMC calls.
+
endif
diff --git a/drivers/rng/Makefile b/drivers/rng/Makefile
index c1f1c616e00..30553c9d6e9 100644
--- a/drivers/rng/Makefile
+++ b/drivers/rng/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_RNG_ARM_RNDR) += arm_rndr.o
obj-$(CONFIG_TPM_RNG) += tpm_rng.o
obj-$(CONFIG_RNG_JH7110) += jh7110_rng.o
obj-$(CONFIG_RNG_TURRIS_RWTM) += turris_rwtm_rng.o
+obj-$(CONFIG_RNG_EXYNOS) += exynos-trng.o
diff --git a/drivers/rng/exynos-trng.c b/drivers/rng/exynos-trng.c
new file mode 100644
index 00000000000..d2479d244ed
--- /dev/null
+++ b/drivers/rng/exynos-trng.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Samsung Exynos TRNG driver (True Random Number Generator).
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <rng.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <asm/io.h>
+#include <linux/arm-smccc.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/time.h>
+
+#define EXYNOS_TRNG_CLKDIV 0x0
+#define EXYNOS_TRNG_CLKDIV_MASK GENMASK(15, 0)
+#define EXYNOS_TRNG_CLOCK_RATE 500000
+
+#define EXYNOS_TRNG_CTRL 0x20
+#define EXYNOS_TRNG_CTRL_RNGEN BIT(31)
+
+#define EXYNOS_TRNG_POST_CTRL 0x30
+#define EXYNOS_TRNG_ONLINE_CTRL 0x40
+#define EXYNOS_TRNG_ONLINE_STAT 0x44
+#define EXYNOS_TRNG_ONLINE_MAXCHI2 0x48
+#define EXYNOS_TRNG_FIFO_CTRL 0x50
+#define EXYNOS_TRNG_FIFO_0 0x80
+#define EXYNOS_TRNG_FIFO_1 0x84
+#define EXYNOS_TRNG_FIFO_2 0x88
+#define EXYNOS_TRNG_FIFO_3 0x8c
+#define EXYNOS_TRNG_FIFO_4 0x90
+#define EXYNOS_TRNG_FIFO_5 0x94
+#define EXYNOS_TRNG_FIFO_6 0x98
+#define EXYNOS_TRNG_FIFO_7 0x9c
+#define EXYNOS_TRNG_FIFO_LEN 8
+#define EXYNOS_TRNG_FIFO_TIMEOUT (1 * USEC_PER_SEC)
+
+#define EXYNOS_SMC_CALL_VAL(func_num) \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \
+ ARM_SMCCC_SMC_32, \
+ ARM_SMCCC_OWNER_SIP, \
+ func_num)
+
+/* SMC command for DTRNG access */
+#define SMC_CMD_RANDOM EXYNOS_SMC_CALL_VAL(0x1012)
+
+/* SMC_CMD_RANDOM: arguments */
+#define HWRNG_INIT 0x0
+#define HWRNG_EXIT 0x1
+#define HWRNG_GET_DATA 0x2
+
+/* SMC_CMD_RANDOM: return values */
+#define HWRNG_RET_OK 0x0
+#define HWRNG_RET_RETRY_ERROR 0x2
+
+#define HWRNG_MAX_TRIES 100
+
+/**
+ * struct exynos_trng_variant - Chip specific data
+ *
+ * @smc: Set "true" if TRNG block has to be accessed via SMC calls
+ * @init: (Optional) TRNG initialization function to call on probe
+ * @exit: (Optional) TRNG deinitialization function to call on remove
+ * @read: Function to read the random data from TRNG block
+ */
+struct exynos_trng_variant {
+ bool smc;
+ int (*init)(struct udevice *dev);
+ void (*exit)(struct udevice *dev);
+ int (*read)(struct udevice *dev, void *data, size_t len);
+};
+
+/**
+ * struct exynos_trng_priv - Driver's private data
+ *
+ * @base: Base address of MMIO registers of TRNG block
+ * @clk: Operating clock (needed for TRNG block functioning)
+ * @pclk: Bus clock (needed for interfacing the TRNG block registers)
+ * @data: Chip specific data
+ */
+struct exynos_trng_priv {
+ void __iomem *base;
+ struct clk *clk;
+ struct clk *pclk;
+ const struct exynos_trng_variant *data;
+};
+
+static int exynos_trng_read_reg(struct udevice *dev, void *data, size_t len)
+{
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+ int val;
+
+ len = min_t(size_t, len, EXYNOS_TRNG_FIFO_LEN * 4);
+ writel_relaxed(len * 8, trng->base + EXYNOS_TRNG_FIFO_CTRL);
+ val = readl_poll_timeout(trng->base + EXYNOS_TRNG_FIFO_CTRL, val,
+ val == 0, EXYNOS_TRNG_FIFO_TIMEOUT);
+ if (val < 0)
+ return val;
+
+ memcpy_fromio(data, trng->base + EXYNOS_TRNG_FIFO_0, len);
+
+ return 0;
+}
+
+static int exynos_trng_read_smc(struct udevice *dev, void *data, size_t len)
+{
+ struct arm_smccc_res res;
+ unsigned int copied = 0;
+ u32 *buf = data;
+ int tries = 0;
+
+ while (copied < len) {
+ arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0, 0, 0, 0, 0,
+ &res);
+ switch (res.a0) {
+ case HWRNG_RET_OK:
+ *buf++ = res.a2;
+ *buf++ = res.a3;
+ copied += 8;
+ tries = 0;
+ break;
+ case HWRNG_RET_RETRY_ERROR:
+ if (++tries >= HWRNG_MAX_TRIES)
+ return -EIO;
+ udelay(10);
+ break;
+ default:
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int exynos_trng_init_reg(struct udevice *dev)
+{
+ const u32 max_div = EXYNOS_TRNG_CLKDIV_MASK;
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+ unsigned long sss_rate;
+ u32 div;
+
+ sss_rate = clk_get_rate(trng->clk);
+
+ /*
+ * For most TRNG circuits the clock frequency of under 500 kHz is safe.
+ * The clock divider should be an even number.
+ */
+ div = sss_rate / EXYNOS_TRNG_CLOCK_RATE;
+ div -= div % 2; /* make sure it's even */
+ if (div > max_div) {
+ dev_err(dev, "Clock divider too large: %u", div);
+ return -ERANGE;
+ }
+ writel_relaxed(div, trng->base + EXYNOS_TRNG_CLKDIV);
+
+ /* Enable the generator */
+ writel_relaxed(EXYNOS_TRNG_CTRL_RNGEN, trng->base + EXYNOS_TRNG_CTRL);
+
+ /* Disable post-processing */
+ writel_relaxed(0, trng->base + EXYNOS_TRNG_POST_CTRL);
+
+ return 0;
+}
+
+static int exynos_trng_init_smc(struct udevice *dev)
+{
+ struct arm_smccc_res res;
+ int ret = 0;
+
+ arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != HWRNG_RET_OK) {
+ dev_err(dev, "SMC command for TRNG init failed (%d)\n",
+ (int)res.a0);
+ ret = -EIO;
+ }
+ if ((int)res.a0 == -1)
+ dev_info(dev, "Make sure LDFW is loaded\n");
+
+ return ret;
+}
+
+static void exynos_trng_exit_smc(struct udevice *dev)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, &res);
+}
+
+static int exynos_trng_read(struct udevice *dev, void *data, size_t len)
+{
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+
+ return trng->data->read(dev, data, len);
+}
+
+static int exynos_trng_of_to_plat(struct udevice *dev)
+{
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+
+ trng->data = (struct exynos_trng_variant *)dev_get_driver_data(dev);
+ if (!trng->data->smc) {
+ trng->base = dev_read_addr_ptr(dev);
+ if (!trng->base)
+ return -EINVAL;
+ }
+
+ trng->clk = devm_clk_get(dev, "secss");
+ if (IS_ERR(trng->clk))
+ return PTR_ERR(trng->clk);
+
+ trng->pclk = devm_clk_get_optional(dev, "pclk");
+ if (IS_ERR(trng->pclk))
+ return PTR_ERR(trng->pclk);
+
+ return 0;
+}
+
+static int exynos_trng_probe(struct udevice *dev)
+{
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+ int ret;
+
+ ret = clk_enable(trng->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_enable(trng->clk);
+ if (ret)
+ return ret;
+
+ if (trng->data->init)
+ ret = trng->data->init(dev);
+
+ return ret;
+}
+
+static int exynos_trng_remove(struct udevice *dev)
+{
+ struct exynos_trng_priv *trng = dev_get_priv(dev);
+
+ if (trng->data->exit)
+ trng->data->exit(dev);
+
+ /* Keep SSS clocks enabled, they are needed for EL3_MON and kernel */
+
+ return 0;
+}
+
+static const struct dm_rng_ops exynos_trng_ops = {
+ .read = exynos_trng_read,
+};
+
+static const struct exynos_trng_variant exynos5250_trng_data = {
+ .init = exynos_trng_init_reg,
+ .read = exynos_trng_read_reg,
+};
+
+static const struct exynos_trng_variant exynos850_trng_data = {
+ .smc = true,
+ .init = exynos_trng_init_smc,
+ .exit = exynos_trng_exit_smc,
+ .read = exynos_trng_read_smc,
+};
+
+static const struct udevice_id exynos_trng_match[] = {
+ {
+ .compatible = "samsung,exynos5250-trng",
+ .data = (ulong)&exynos5250_trng_data,
+ }, {
+ .compatible = "samsung,exynos850-trng",
+ .data = (ulong)&exynos850_trng_data,
+ },
+ { },
+};
+
+U_BOOT_DRIVER(exynos_trng) = {
+ .name = "exynos-trng",
+ .id = UCLASS_RNG,
+ .of_match = exynos_trng_match,
+ .of_to_plat = exynos_trng_of_to_plat,
+ .probe = exynos_trng_probe,
+ .remove = exynos_trng_remove,
+ .ops = &exynos_trng_ops,
+ .priv_auto = sizeof(struct exynos_trng_priv),
+};