Merge branch 'qcom-main' of https://gitlab.denx.de/u-boot/custodians/u-boot-snapdragon

* Qualcomm platforms >~2016 gain support for the RPMh (Resource Power Manager)
  peripheral which is used to control most regulators. The RB5 is now able to
  power up its USB VBUS regulator via the rpmh regulator driver. Git history
  from the original Linux driver is preserved for ease of maintenance.
* IPQ40xx SoCs gain ethernet networking support via the new ESS EDMA driver.
diff --git a/arch/arm/dts/exynos850-e850-96-u-boot.dtsi b/arch/arm/dts/exynos850-e850-96-u-boot.dtsi
index 6d7148f..3aa5d8b 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 cad8bb0..3fee5a4 100644
--- a/arch/arm/mach-exynos/Kconfig
+++ b/arch/arm/mach-exynos/Kconfig
@@ -250,6 +250,8 @@
 	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 301c223..71d46ea 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 a00d81b..c5cef6f 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 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 0000000..f36f90b
--- /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 0000000..82a0b22
--- /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 0000000..472664e
--- /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 5bf4831..615c99f 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 a9d62ff..37d6bbb 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 38b9968..2949da2 100644
--- a/configs/e850-96_defconfig
+++ b/configs/e850-96_defconfig
@@ -11,6 +11,7 @@
 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 0c09ba0..8cbc626 100644
--- a/drivers/clk/exynos/clk-exynos850.c
+++ b/drivers/clk/exynos/clk-exynos850.c
@@ -323,14 +323,18 @@
 /* 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 @@
 	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 @@
 	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 0de3638..fccc5f5 100644
--- a/drivers/net/essedma.c
+++ b/drivers/net/essedma.c
@@ -163,7 +163,7 @@
 #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 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 e0b5c96..067cb44 100644
--- a/drivers/net/essedma.h
+++ b/drivers/net/essedma.h
@@ -192,7 +192,7 @@
 
 /* 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 5758ae1..b35d8c6 100644
--- a/drivers/rng/Kconfig
+++ b/drivers/rng/Kconfig
@@ -120,4 +120,17 @@
 	  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 c1f1c61..30553c9 100644
--- a/drivers/rng/Makefile
+++ b/drivers/rng/Makefile
@@ -18,3 +18,4 @@
 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 0000000..d2479d2
--- /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),
+};