aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-02-20 08:02:49 -0500
committerTom Rini <trini@konsulko.com>2024-02-20 08:02:49 -0500
commitbebf916f9eb13aaf5bbf83fbd33204df5c6c9f8e (patch)
treece68a4801a1f9c82516aec08e641c3d29cb45c2e /drivers
parent3e6f2a94bfc25f1782ce2d45db27f47ec781feb1 (diff)
parent1e81d12e34e874319e041652198a4ba561d751ab (diff)
downloadu-boot-bebf916f9eb13aaf5bbf83fbd33204df5c6c9f8e.zip
u-boot-bebf916f9eb13aaf5bbf83fbd33204df5c6c9f8e.tar.gz
u-boot-bebf916f9eb13aaf5bbf83fbd33204df5c6c9f8e.tar.bz2
Merge https://gitlab.denx.de/u-boot/custodians/u-boot-samsung
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/exynos/Kconfig7
-rw-r--r--drivers/clk/exynos/Makefile11
-rw-r--r--drivers/clk/exynos/clk-exynos7420.c25
-rw-r--r--drivers/clk/exynos/clk-exynos850.c189
-rw-r--r--drivers/clk/exynos/clk-pll.c167
-rw-r--r--drivers/clk/exynos/clk-pll.h16
-rw-r--r--drivers/clk/exynos/clk.c121
-rw-r--r--drivers/clk/exynos/clk.h228
-rw-r--r--drivers/pinctrl/exynos/Kconfig8
-rw-r--r--drivers/pinctrl/exynos/Makefile1
-rw-r--r--drivers/pinctrl/exynos/pinctrl-exynos850.c125
-rw-r--r--drivers/serial/serial_s5p.c1
-rw-r--r--drivers/soc/Kconfig1
-rw-r--r--drivers/soc/Makefile1
-rw-r--r--drivers/soc/samsung/Kconfig33
-rw-r--r--drivers/soc/samsung/Makefile4
-rw-r--r--drivers/soc/samsung/exynos-pmu.c102
-rw-r--r--drivers/soc/samsung/exynos-usi.c208
18 files changed, 1225 insertions, 23 deletions
diff --git a/drivers/clk/exynos/Kconfig b/drivers/clk/exynos/Kconfig
index eb0efa9..85ce9d6 100644
--- a/drivers/clk/exynos/Kconfig
+++ b/drivers/clk/exynos/Kconfig
@@ -15,4 +15,11 @@ config CLK_EXYNOS7420
This enables common clock driver support for platforms based
on Samsung Exynos7420 SoC.
+config CLK_EXYNOS850
+ bool "Clock driver for Samsung's Exynos850 SoC"
+ select CLK_CCF
+ help
+ This enables common clock driver support for platforms based
+ on Samsung Exynos850 SoC.
+
endmenu
diff --git a/drivers/clk/exynos/Makefile b/drivers/clk/exynos/Makefile
index c9f29c8..734100e 100644
--- a/drivers/clk/exynos/Makefile
+++ b/drivers/clk/exynos/Makefile
@@ -1,7 +1,12 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2016 Samsung Electronics
-# Thomas Abraham <thomas.ab@samsung.com>
+# Copyright (C) 2023 Linaro Ltd.
+#
+# Authors:
+# Thomas Abraham <thomas.ab@samsung.com>
+# Sam Protsenko <semen.protsenko@linaro.org>
-obj-y += clk-pll.o
-obj-$(CONFIG_CLK_EXYNOS7420) += clk-exynos7420.o
+obj-$(CONFIG_$(SPL_TPL_)CLK_CCF) += clk.o clk-pll.o
+obj-$(CONFIG_CLK_EXYNOS7420) += clk-exynos7420.o
+obj-$(CONFIG_CLK_EXYNOS850) += clk-exynos850.o
diff --git a/drivers/clk/exynos/clk-exynos7420.c b/drivers/clk/exynos/clk-exynos7420.c
index 7d869eb..9caa932 100644
--- a/drivers/clk/exynos/clk-exynos7420.c
+++ b/drivers/clk/exynos/clk-exynos7420.c
@@ -10,8 +10,15 @@
#include <errno.h>
#include <clk-uclass.h>
#include <asm/io.h>
+#include <div64.h>
#include <dt-bindings/clock/exynos7420-clk.h>
-#include "clk-pll.h"
+
+#define PLL145X_MDIV_SHIFT 16
+#define PLL145X_MDIV_MASK 0x3ff
+#define PLL145X_PDIV_SHIFT 8
+#define PLL145X_PDIV_MASK 0x3f
+#define PLL145X_SDIV_SHIFT 0
+#define PLL145X_SDIV_MASK 0x7
#define DIVIDER(reg, shift, mask) \
(((readl(reg) >> shift) & mask) + 1)
@@ -64,6 +71,22 @@ struct exynos7420_clk_top0_priv {
unsigned long sclk_uart2;
};
+static unsigned long pll145x_get_rate(unsigned int *con1,
+ unsigned long fin_freq)
+{
+ unsigned long pll_con1 = readl(con1);
+ unsigned long mdiv, sdiv, pdiv;
+ u64 fvco = fin_freq;
+
+ mdiv = (pll_con1 >> PLL145X_MDIV_SHIFT) & PLL145X_MDIV_MASK;
+ pdiv = (pll_con1 >> PLL145X_PDIV_SHIFT) & PLL145X_PDIV_MASK;
+ sdiv = (pll_con1 >> PLL145X_SDIV_SHIFT) & PLL145X_SDIV_MASK;
+
+ fvco *= mdiv;
+ do_div(fvco, (pdiv << sdiv));
+ return (unsigned long)fvco;
+}
+
static ulong exynos7420_topc_get_rate(struct clk *clk)
{
struct exynos7420_clk_topc_priv *priv = dev_get_priv(clk->dev);
diff --git a/drivers/clk/exynos/clk-exynos850.c b/drivers/clk/exynos/clk-exynos850.c
new file mode 100644
index 0000000..cf94a3e
--- /dev/null
+++ b/drivers/clk/exynos/clk-exynos850.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Samsung Exynos850 clock driver.
+ * Copyright (c) 2023 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ */
+
+#include <dm.h>
+#include <asm/io.h>
+#include <dt-bindings/clock/exynos850.h>
+#include "clk.h"
+
+/* ---- CMU_TOP ------------------------------------------------------------- */
+
+/* Register Offset definitions for CMU_TOP (0x120e0000) */
+#define PLL_CON0_PLL_MMC 0x0100
+#define PLL_CON3_PLL_MMC 0x010c
+#define PLL_CON0_PLL_SHARED0 0x0140
+#define PLL_CON3_PLL_SHARED0 0x014c
+#define PLL_CON0_PLL_SHARED1 0x0180
+#define PLL_CON3_PLL_SHARED1 0x018c
+#define CLK_CON_MUX_MUX_CLKCMU_PERI_BUS 0x1070
+#define CLK_CON_MUX_MUX_CLKCMU_PERI_IP 0x1074
+#define CLK_CON_MUX_MUX_CLKCMU_PERI_UART 0x1078
+#define CLK_CON_DIV_CLKCMU_PERI_BUS 0x187c
+#define CLK_CON_DIV_CLKCMU_PERI_IP 0x1880
+#define CLK_CON_DIV_CLKCMU_PERI_UART 0x1884
+#define CLK_CON_DIV_PLL_SHARED0_DIV2 0x188c
+#define CLK_CON_DIV_PLL_SHARED0_DIV3 0x1890
+#define CLK_CON_DIV_PLL_SHARED0_DIV4 0x1894
+#define CLK_CON_DIV_PLL_SHARED1_DIV2 0x1898
+#define CLK_CON_DIV_PLL_SHARED1_DIV3 0x189c
+#define CLK_CON_DIV_PLL_SHARED1_DIV4 0x18a0
+#define CLK_CON_GAT_GATE_CLKCMU_PERI_BUS 0x2080
+#define CLK_CON_GAT_GATE_CLKCMU_PERI_IP 0x2084
+#define CLK_CON_GAT_GATE_CLKCMU_PERI_UART 0x2088
+
+static const struct samsung_pll_clock top_pure_pll_clks[] = {
+ PLL(pll_0822x, CLK_FOUT_SHARED0_PLL, "fout_shared0_pll", "clock-oscclk",
+ PLL_CON3_PLL_SHARED0),
+ PLL(pll_0822x, CLK_FOUT_SHARED1_PLL, "fout_shared1_pll", "clock-oscclk",
+ PLL_CON3_PLL_SHARED1),
+ PLL(pll_0831x, CLK_FOUT_MMC_PLL, "fout_mmc_pll", "clock-oscclk",
+ PLL_CON3_PLL_MMC),
+};
+
+/* List of parent clocks for Muxes in CMU_TOP */
+PNAME(mout_shared0_pll_p) = { "clock-oscclk", "fout_shared0_pll" };
+PNAME(mout_shared1_pll_p) = { "clock-oscclk", "fout_shared1_pll" };
+PNAME(mout_mmc_pll_p) = { "clock-oscclk", "fout_mmc_pll" };
+/* List of parent clocks for Muxes in CMU_TOP: for CMU_PERI */
+PNAME(mout_peri_bus_p) = { "dout_shared0_div4", "dout_shared1_div4" };
+PNAME(mout_peri_uart_p) = { "clock-oscclk", "dout_shared0_div4",
+ "dout_shared1_div4", "clock-oscclk" };
+PNAME(mout_peri_ip_p) = { "clock-oscclk", "dout_shared0_div4",
+ "dout_shared1_div4", "clock-oscclk" };
+
+static const struct samsung_mux_clock top_pure_mux_clks[] = {
+ MUX(CLK_MOUT_SHARED0_PLL, "mout_shared0_pll", mout_shared0_pll_p,
+ PLL_CON0_PLL_SHARED0, 4, 1),
+ MUX(CLK_MOUT_SHARED1_PLL, "mout_shared1_pll", mout_shared1_pll_p,
+ PLL_CON0_PLL_SHARED1, 4, 1),
+ MUX(CLK_MOUT_MMC_PLL, "mout_mmc_pll", mout_mmc_pll_p,
+ PLL_CON0_PLL_MMC, 4, 1),
+};
+
+static const struct samsung_mux_clock top_peri_mux_clks[] = {
+ MUX(CLK_MOUT_PERI_BUS, "mout_peri_bus", mout_peri_bus_p,
+ CLK_CON_MUX_MUX_CLKCMU_PERI_BUS, 0, 1),
+ MUX(CLK_MOUT_PERI_UART, "mout_peri_uart", mout_peri_uart_p,
+ CLK_CON_MUX_MUX_CLKCMU_PERI_UART, 0, 2),
+ MUX(CLK_MOUT_PERI_IP, "mout_peri_ip", mout_peri_ip_p,
+ CLK_CON_MUX_MUX_CLKCMU_PERI_IP, 0, 2),
+};
+
+static const struct samsung_div_clock top_pure_div_clks[] = {
+ DIV(CLK_DOUT_SHARED0_DIV3, "dout_shared0_div3", "mout_shared0_pll",
+ CLK_CON_DIV_PLL_SHARED0_DIV3, 0, 2),
+ DIV(CLK_DOUT_SHARED0_DIV2, "dout_shared0_div2", "mout_shared0_pll",
+ CLK_CON_DIV_PLL_SHARED0_DIV2, 0, 1),
+ DIV(CLK_DOUT_SHARED1_DIV3, "dout_shared1_div3", "mout_shared1_pll",
+ CLK_CON_DIV_PLL_SHARED1_DIV3, 0, 2),
+ DIV(CLK_DOUT_SHARED1_DIV2, "dout_shared1_div2", "mout_shared1_pll",
+ CLK_CON_DIV_PLL_SHARED1_DIV2, 0, 1),
+ DIV(CLK_DOUT_SHARED0_DIV4, "dout_shared0_div4", "dout_shared0_div2",
+ CLK_CON_DIV_PLL_SHARED0_DIV4, 0, 1),
+ DIV(CLK_DOUT_SHARED1_DIV4, "dout_shared1_div4", "dout_shared1_div2",
+ CLK_CON_DIV_PLL_SHARED1_DIV4, 0, 1),
+};
+
+static const struct samsung_div_clock top_peri_div_clks[] = {
+ DIV(CLK_DOUT_PERI_BUS, "dout_peri_bus", "gout_peri_bus",
+ CLK_CON_DIV_CLKCMU_PERI_BUS, 0, 4),
+ DIV(CLK_DOUT_PERI_UART, "dout_peri_uart", "gout_peri_uart",
+ CLK_CON_DIV_CLKCMU_PERI_UART, 0, 4),
+ DIV(CLK_DOUT_PERI_IP, "dout_peri_ip", "gout_peri_ip",
+ CLK_CON_DIV_CLKCMU_PERI_IP, 0, 4),
+};
+
+static const struct samsung_gate_clock top_peri_gate_clks[] = {
+ GATE(CLK_GOUT_PERI_BUS, "gout_peri_bus", "mout_peri_bus",
+ CLK_CON_GAT_GATE_CLKCMU_PERI_BUS, 21, 0, 0),
+ GATE(CLK_GOUT_PERI_UART, "gout_peri_uart", "mout_peri_uart",
+ CLK_CON_GAT_GATE_CLKCMU_PERI_UART, 21, 0, 0),
+ GATE(CLK_GOUT_PERI_IP, "gout_peri_ip", "mout_peri_ip",
+ CLK_CON_GAT_GATE_CLKCMU_PERI_IP, 21, 0, 0),
+};
+
+static const struct samsung_clk_group top_cmu_clks[] = {
+ /* CMU_TOP_PURECLKCOMP */
+ { S_CLK_PLL, top_pure_pll_clks, ARRAY_SIZE(top_pure_pll_clks) },
+ { S_CLK_MUX, top_pure_mux_clks, ARRAY_SIZE(top_pure_mux_clks) },
+ { S_CLK_DIV, top_pure_div_clks, ARRAY_SIZE(top_pure_div_clks) },
+
+ /* CMU_TOP clocks for CMU_PERI */
+ { S_CLK_MUX, top_peri_mux_clks, ARRAY_SIZE(top_peri_mux_clks) },
+ { S_CLK_GATE, top_peri_gate_clks, ARRAY_SIZE(top_peri_gate_clks) },
+ { S_CLK_DIV, top_peri_div_clks, ARRAY_SIZE(top_peri_div_clks) },
+};
+
+static int exynos850_cmu_top_probe(struct udevice *dev)
+{
+ return samsung_cmu_register_one(dev, top_cmu_clks,
+ ARRAY_SIZE(top_cmu_clks));
+}
+
+static const struct udevice_id exynos850_cmu_top_ids[] = {
+ { .compatible = "samsung,exynos850-cmu-top" },
+ { }
+};
+
+U_BOOT_DRIVER(exynos850_cmu_top) = {
+ .name = "exynos850-cmu-top",
+ .id = UCLASS_CLK,
+ .of_match = exynos850_cmu_top_ids,
+ .ops = &ccf_clk_ops,
+ .probe = exynos850_cmu_top_probe,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+/* ---- CMU_PERI ------------------------------------------------------------ */
+
+/* Register Offset definitions for CMU_PERI (0x10030000) */
+#define PLL_CON0_MUX_CLKCMU_PERI_BUS_USER 0x0600
+#define PLL_CON0_MUX_CLKCMU_PERI_UART_USER 0x0630
+#define CLK_CON_GAT_GOUT_PERI_UART_IPCLK 0x20a8
+#define CLK_CON_GAT_GOUT_PERI_UART_PCLK 0x20ac
+
+/* List of parent clocks for Muxes in CMU_PERI */
+PNAME(mout_peri_bus_user_p) = { "clock-oscclk", "dout_peri_bus" };
+PNAME(mout_peri_uart_user_p) = { "clock-oscclk", "dout_peri_uart" };
+
+static const struct samsung_mux_clock peri_mux_clks[] = {
+ MUX(CLK_MOUT_PERI_BUS_USER, "mout_peri_bus_user", mout_peri_bus_user_p,
+ PLL_CON0_MUX_CLKCMU_PERI_BUS_USER, 4, 1),
+ MUX(CLK_MOUT_PERI_UART_USER, "mout_peri_uart_user",
+ mout_peri_uart_user_p, PLL_CON0_MUX_CLKCMU_PERI_UART_USER, 4, 1),
+};
+
+static const struct samsung_gate_clock peri_gate_clks[] = {
+ GATE(CLK_GOUT_UART_IPCLK, "gout_uart_ipclk", "mout_peri_uart_user",
+ CLK_CON_GAT_GOUT_PERI_UART_IPCLK, 21, 0, 0),
+ GATE(CLK_GOUT_UART_PCLK, "gout_uart_pclk", "mout_peri_bus_user",
+ CLK_CON_GAT_GOUT_PERI_UART_PCLK, 21, 0, 0),
+};
+
+static const struct samsung_clk_group peri_cmu_clks[] = {
+ { S_CLK_MUX, peri_mux_clks, ARRAY_SIZE(peri_mux_clks) },
+ { S_CLK_GATE, peri_gate_clks, ARRAY_SIZE(peri_gate_clks) },
+};
+
+static int exynos850_cmu_peri_probe(struct udevice *dev)
+{
+ return samsung_register_cmu(dev, peri_cmu_clks, exynos850_cmu_top);
+}
+
+static const struct udevice_id exynos850_cmu_peri_ids[] = {
+ { .compatible = "samsung,exynos850-cmu-peri" },
+ { }
+};
+
+U_BOOT_DRIVER(exynos850_cmu_peri) = {
+ .name = "exynos850-cmu-peri",
+ .id = UCLASS_CLK,
+ .of_match = exynos850_cmu_peri_ids,
+ .ops = &ccf_clk_ops,
+ .probe = exynos850_cmu_peri_probe,
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/exynos/clk-pll.c b/drivers/clk/exynos/clk-pll.c
index 407fc71..4aacbc2 100644
--- a/drivers/clk/exynos/clk-pll.c
+++ b/drivers/clk/exynos/clk-pll.c
@@ -1,32 +1,167 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Exynos PLL helper functions for clock drivers.
* Copyright (C) 2016 Samsung Electronics
- * Thomas Abraham <thomas.ab@samsung.com>
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Authors:
+ * Thomas Abraham <thomas.ab@samsung.com>
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * This file contains the utility functions to register the pll clocks.
*/
-#include <common.h>
#include <asm/io.h>
#include <div64.h>
+#include <malloc.h>
+#include <clk-uclass.h>
+#include <dm/device.h>
+#include <clk.h>
+#include "clk.h"
-#define PLL145X_MDIV_SHIFT 16
-#define PLL145X_MDIV_MASK 0x3ff
-#define PLL145X_PDIV_SHIFT 8
-#define PLL145X_PDIV_MASK 0x3f
-#define PLL145X_SDIV_SHIFT 0
-#define PLL145X_SDIV_MASK 0x7
+#define UBOOT_DM_CLK_SAMSUNG_PLL0822X "samsung_clk_pll0822x"
+#define UBOOT_DM_CLK_SAMSUNG_PLL0831X "samsung_clk_pll0831x"
-unsigned long pll145x_get_rate(unsigned int *con1, unsigned long fin_freq)
+struct samsung_clk_pll {
+ struct clk clk;
+ void __iomem *con_reg;
+ enum samsung_pll_type type;
+};
+
+#define to_clk_pll(_clk) container_of(_clk, struct samsung_clk_pll, clk)
+
+/*
+ * PLL0822x Clock Type
+ */
+
+#define PLL0822X_MDIV_MASK 0x3ff
+#define PLL0822X_PDIV_MASK 0x3f
+#define PLL0822X_SDIV_MASK 0x7
+#define PLL0822X_MDIV_SHIFT 16
+#define PLL0822X_PDIV_SHIFT 8
+#define PLL0822X_SDIV_SHIFT 0
+
+static unsigned long samsung_pll0822x_recalc_rate(struct clk *clk)
{
- unsigned long pll_con1 = readl(con1);
- unsigned long mdiv, sdiv, pdiv;
- uint64_t fvco = fin_freq;
+ struct samsung_clk_pll *pll = to_clk_pll(clk);
+ u32 mdiv, pdiv, sdiv, pll_con3;
+ u64 fvco = clk_get_parent_rate(clk);
- mdiv = (pll_con1 >> PLL145X_MDIV_SHIFT) & PLL145X_MDIV_MASK;
- pdiv = (pll_con1 >> PLL145X_PDIV_SHIFT) & PLL145X_PDIV_MASK;
- sdiv = (pll_con1 >> PLL145X_SDIV_SHIFT) & PLL145X_SDIV_MASK;
+ pll_con3 = readl_relaxed(pll->con_reg);
+ mdiv = (pll_con3 >> PLL0822X_MDIV_SHIFT) & PLL0822X_MDIV_MASK;
+ pdiv = (pll_con3 >> PLL0822X_PDIV_SHIFT) & PLL0822X_PDIV_MASK;
+ sdiv = (pll_con3 >> PLL0822X_SDIV_SHIFT) & PLL0822X_SDIV_MASK;
fvco *= mdiv;
do_div(fvco, (pdiv << sdiv));
return (unsigned long)fvco;
}
+
+static const struct clk_ops samsung_pll0822x_clk_min_ops = {
+ .get_rate = samsung_pll0822x_recalc_rate,
+};
+
+/*
+ * PLL0831x Clock Type
+ */
+
+#define PLL0831X_KDIV_MASK 0xffff
+#define PLL0831X_MDIV_MASK 0x1ff
+#define PLL0831X_PDIV_MASK 0x3f
+#define PLL0831X_SDIV_MASK 0x7
+#define PLL0831X_MDIV_SHIFT 16
+#define PLL0831X_PDIV_SHIFT 8
+#define PLL0831X_SDIV_SHIFT 0
+#define PLL0831X_KDIV_SHIFT 0
+
+static unsigned long samsung_pll0831x_recalc_rate(struct clk *clk)
+{
+ struct samsung_clk_pll *pll = to_clk_pll(clk);
+ u32 mdiv, pdiv, sdiv, pll_con3, pll_con5;
+ s16 kdiv;
+ u64 fvco = clk_get_parent_rate(clk);
+
+ pll_con3 = readl_relaxed(pll->con_reg);
+ pll_con5 = readl_relaxed(pll->con_reg + 8);
+ mdiv = (pll_con3 >> PLL0831X_MDIV_SHIFT) & PLL0831X_MDIV_MASK;
+ pdiv = (pll_con3 >> PLL0831X_PDIV_SHIFT) & PLL0831X_PDIV_MASK;
+ sdiv = (pll_con3 >> PLL0831X_SDIV_SHIFT) & PLL0831X_SDIV_MASK;
+ kdiv = (s16)((pll_con5 >> PLL0831X_KDIV_SHIFT) & PLL0831X_KDIV_MASK);
+
+ fvco *= (mdiv << 16) + kdiv;
+ do_div(fvco, (pdiv << sdiv));
+ fvco >>= 16;
+
+ return (unsigned long)fvco;
+}
+
+static const struct clk_ops samsung_pll0831x_clk_min_ops = {
+ .get_rate = samsung_pll0831x_recalc_rate,
+};
+
+static struct clk *_samsung_clk_register_pll(void __iomem *base,
+ const struct samsung_pll_clock *pll_clk)
+{
+ struct samsung_clk_pll *pll;
+ struct clk *clk;
+ const char *drv_name;
+ int ret;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->con_reg = base + pll_clk->con_offset;
+ pll->type = pll_clk->type;
+ clk = &pll->clk;
+ clk->flags = pll_clk->flags;
+
+ switch (pll_clk->type) {
+ case pll_0822x:
+ drv_name = UBOOT_DM_CLK_SAMSUNG_PLL0822X;
+ break;
+ case pll_0831x:
+ drv_name = UBOOT_DM_CLK_SAMSUNG_PLL0831X;
+ break;
+ default:
+ kfree(pll);
+ return ERR_PTR(-ENODEV);
+ }
+
+ ret = clk_register(clk, drv_name, pll_clk->name, pll_clk->parent_name);
+ if (ret) {
+ kfree(pll);
+ return ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+void samsung_clk_register_pll(void __iomem *base,
+ const struct samsung_pll_clock *clk_list,
+ unsigned int nr_clk)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < nr_clk; cnt++) {
+ struct clk *clk;
+ const struct samsung_pll_clock *pll_clk;
+
+ pll_clk = &clk_list[cnt];
+ clk = _samsung_clk_register_pll(base, pll_clk);
+ clk_dm(pll_clk->id, clk);
+ }
+}
+
+U_BOOT_DRIVER(samsung_pll0822x_clk) = {
+ .name = UBOOT_DM_CLK_SAMSUNG_PLL0822X,
+ .id = UCLASS_CLK,
+ .ops = &samsung_pll0822x_clk_min_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRIVER(samsung_pll0831x_clk) = {
+ .name = UBOOT_DM_CLK_SAMSUNG_PLL0831X,
+ .id = UCLASS_CLK,
+ .ops = &samsung_pll0831x_clk_min_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/exynos/clk-pll.h b/drivers/clk/exynos/clk-pll.h
index 7b7af5e..bd79309 100644
--- a/drivers/clk/exynos/clk-pll.h
+++ b/drivers/clk/exynos/clk-pll.h
@@ -1,13 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
- * Exynos PLL helper functions for clock drivers.
* Copyright (C) 2016 Samsung Electronics
- * Thomas Abraham <thomas.ab@samsung.com>
+ * Copyright (C) 2023 Linaro Ltd.
+ *
+ * Authors:
+ * Thomas Abraham <thomas.ab@samsung.com>
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Common Clock Framework support for all PLL's in Samsung platforms.
*/
#ifndef __EXYNOS_CLK_PLL_H
#define __EXYNOS_CLK_PLL_H
-unsigned long pll145x_get_rate(unsigned int *con1, unsigned long fin_freq);
+#include <linux/clk-provider.h>
+
+enum samsung_pll_type {
+ pll_0822x,
+ pll_0831x,
+};
#endif /* __EXYNOS_CLK_PLL_H */
diff --git a/drivers/clk/exynos/clk.c b/drivers/clk/exynos/clk.c
new file mode 100644
index 0000000..430767f
--- /dev/null
+++ b/drivers/clk/exynos/clk.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * This file includes utility functions to register clocks to common
+ * clock framework for Samsung platforms.
+ */
+
+#include <dm.h>
+#include "clk.h"
+
+void samsung_clk_register_mux(void __iomem *base,
+ const struct samsung_mux_clock *clk_list,
+ unsigned int nr_clk)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < nr_clk; cnt++) {
+ struct clk *clk;
+ const struct samsung_mux_clock *m;
+
+ m = &clk_list[cnt];
+ clk = clk_register_mux(NULL, m->name, m->parent_names,
+ m->num_parents, m->flags, base + m->offset, m->shift,
+ m->width, m->mux_flags);
+ clk_dm(m->id, clk);
+ }
+}
+
+void samsung_clk_register_div(void __iomem *base,
+ const struct samsung_div_clock *clk_list,
+ unsigned int nr_clk)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < nr_clk; cnt++) {
+ struct clk *clk;
+ const struct samsung_div_clock *d;
+
+ d = &clk_list[cnt];
+ clk = clk_register_divider(NULL, d->name, d->parent_name,
+ d->flags, base + d->offset, d->shift,
+ d->width, d->div_flags);
+ clk_dm(d->id, clk);
+ }
+}
+
+void samsung_clk_register_gate(void __iomem *base,
+ const struct samsung_gate_clock *clk_list,
+ unsigned int nr_clk)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < nr_clk; cnt++) {
+ struct clk *clk;
+ const struct samsung_gate_clock *g;
+
+ g = &clk_list[cnt];
+ clk = clk_register_gate(NULL, g->name, g->parent_name,
+ g->flags, base + g->offset, g->bit_idx,
+ g->gate_flags, NULL);
+ clk_dm(g->id, clk);
+ }
+}
+
+typedef void (*samsung_clk_register_fn)(void __iomem *base,
+ const void *clk_list,
+ unsigned int nr_clk);
+
+static const samsung_clk_register_fn samsung_clk_register_fns[] = {
+ [S_CLK_MUX] = (samsung_clk_register_fn)samsung_clk_register_mux,
+ [S_CLK_DIV] = (samsung_clk_register_fn)samsung_clk_register_div,
+ [S_CLK_GATE] = (samsung_clk_register_fn)samsung_clk_register_gate,
+ [S_CLK_PLL] = (samsung_clk_register_fn)samsung_clk_register_pll,
+};
+
+/**
+ * samsung_cmu_register_clocks() - Register provided clock groups
+ * @base: Base address of CMU registers
+ * @clk_groups: list of clock groups
+ * @nr_groups: count of clock groups in @clk_groups
+ *
+ * Having the array of clock groups @clk_groups makes it possible to keep a
+ * correct clocks registration order.
+ */
+void samsung_cmu_register_clocks(void __iomem *base,
+ const struct samsung_clk_group *clk_groups,
+ unsigned int nr_groups)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr_groups; i++) {
+ const struct samsung_clk_group *g = &clk_groups[i];
+
+ samsung_clk_register_fns[g->type](base, g->clk_list, g->nr_clk);
+ }
+}
+
+/**
+ * samsung_cmu_register_one - Register all CMU clocks
+ * @dev: CMU device
+ * @clk_groups: list of CMU clock groups
+ * @nr_groups: count of CMU clock groups in @clk_groups
+ *
+ * Return: 0 on success or negative value on error.
+ */
+int samsung_cmu_register_one(struct udevice *dev,
+ const struct samsung_clk_group *clk_groups,
+ unsigned int nr_groups)
+{
+ void __iomem *base;
+
+ base = dev_read_addr_ptr(dev);
+ if (!base)
+ return -EINVAL;
+
+ samsung_cmu_register_clocks(base, clk_groups, nr_groups);
+
+ return 0;
+}
diff --git a/drivers/clk/exynos/clk.h b/drivers/clk/exynos/clk.h
new file mode 100644
index 0000000..91a51b8
--- /dev/null
+++ b/drivers/clk/exynos/clk.h
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Linaro Ltd.
+ * Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Common Clock Framework support for all Samsung platforms.
+ */
+
+#ifndef __EXYNOS_CLK_H
+#define __EXYNOS_CLK_H
+
+#include <errno.h>
+#include <linux/clk-provider.h>
+#include "clk-pll.h"
+
+/**
+ * struct samsung_mux_clock - information about mux clock
+ * @id: platform specific id of the clock
+ * @name: name of this mux clock
+ * @parent_names: array of pointer to parent clock names
+ * @num_parents: number of parents listed in @parent_names
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the mux
+ * @shift: starting bit location of the mux control bit-field in @reg
+ * @width: width of the mux control bit-field in @reg
+ * @mux_flags: flags for mux-type clock
+ */
+struct samsung_mux_clock {
+ unsigned int id;
+ const char *name;
+ const char * const *parent_names;
+ u8 num_parents;
+ unsigned long flags;
+ unsigned long offset;
+ u8 shift;
+ u8 width;
+ u8 mux_flags;
+};
+
+#define PNAME(x) static const char * const x[]
+
+#define __MUX(_id, cname, pnames, o, s, w, f, mf) \
+ { \
+ .id = _id, \
+ .name = cname, \
+ .parent_names = pnames, \
+ .num_parents = ARRAY_SIZE(pnames), \
+ .flags = (f) | CLK_SET_RATE_NO_REPARENT, \
+ .offset = o, \
+ .shift = s, \
+ .width = w, \
+ .mux_flags = mf, \
+ }
+
+#define MUX(_id, cname, pnames, o, s, w) \
+ __MUX(_id, cname, pnames, o, s, w, 0, 0)
+
+#define MUX_F(_id, cname, pnames, o, s, w, f, mf) \
+ __MUX(_id, cname, pnames, o, s, w, f, mf)
+
+/**
+ * struct samsung_div_clock - information about div clock
+ * @id: platform specific id of the clock
+ * @name: name of this div clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the div
+ * @shift: starting bit location of the div control bit-field in @reg
+ * @width: width of the bitfield
+ * @div_flags: flags for div-type clock
+ */
+struct samsung_div_clock {
+ unsigned int id;
+ const char *name;
+ const char *parent_name;
+ unsigned long flags;
+ unsigned long offset;
+ u8 shift;
+ u8 width;
+ u8 div_flags;
+};
+
+#define __DIV(_id, cname, pname, o, s, w, f, df) \
+ { \
+ .id = _id, \
+ .name = cname, \
+ .parent_name = pname, \
+ .flags = f, \
+ .offset = o, \
+ .shift = s, \
+ .width = w, \
+ .div_flags = df, \
+ }
+
+#define DIV(_id, cname, pname, o, s, w) \
+ __DIV(_id, cname, pname, o, s, w, 0, 0)
+
+#define DIV_F(_id, cname, pname, o, s, w, f, df) \
+ __DIV(_id, cname, pname, o, s, w, f, df)
+
+/**
+ * struct samsung_gate_clock - information about gate clock
+ * @id: platform specific id of the clock
+ * @name: name of this gate clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @offset: offset of the register for configuring the gate
+ * @bit_idx: bit index of the gate control bit-field in @reg
+ * @gate_flags: flags for gate-type clock
+ */
+struct samsung_gate_clock {
+ unsigned int id;
+ const char *name;
+ const char *parent_name;
+ unsigned long flags;
+ unsigned long offset;
+ u8 bit_idx;
+ u8 gate_flags;
+};
+
+#define __GATE(_id, cname, pname, o, b, f, gf) \
+ { \
+ .id = _id, \
+ .name = cname, \
+ .parent_name = pname, \
+ .flags = f, \
+ .offset = o, \
+ .bit_idx = b, \
+ .gate_flags = gf, \
+ }
+
+#define GATE(_id, cname, pname, o, b, f, gf) \
+ __GATE(_id, cname, pname, o, b, f, gf)
+
+/**
+ * struct samsung_pll_clock - information about pll clock
+ * @id: platform specific id of the clock
+ * @name: name of this pll clock
+ * @parent_name: name of the parent clock
+ * @flags: optional flags for basic clock
+ * @con_offset: offset of the register for configuring the PLL
+ * @type: type of PLL to be registered
+ */
+struct samsung_pll_clock {
+ unsigned int id;
+ const char *name;
+ const char *parent_name;
+ unsigned long flags;
+ int con_offset;
+ enum samsung_pll_type type;
+};
+
+#define PLL(_typ, _id, _name, _pname, _con) \
+ { \
+ .id = _id, \
+ .name = _name, \
+ .parent_name = _pname, \
+ .flags = CLK_GET_RATE_NOCACHE, \
+ .con_offset = _con, \
+ .type = _typ, \
+ }
+
+enum samsung_clock_type {
+ S_CLK_MUX,
+ S_CLK_DIV,
+ S_CLK_GATE,
+ S_CLK_PLL,
+};
+
+/**
+ * struct samsung_clock_group - contains a list of clocks of one type
+ * @type: type of clocks this structure contains
+ * @clk_list: list of clocks
+ * @nr_clk: count of clocks in @clk_list
+ */
+struct samsung_clk_group {
+ enum samsung_clock_type type;
+ const void *clk_list;
+ unsigned int nr_clk;
+};
+
+void samsung_clk_register_mux(void __iomem *base,
+ const struct samsung_mux_clock *clk_list,
+ unsigned int nr_clk);
+void samsung_clk_register_div(void __iomem *base,
+ const struct samsung_div_clock *clk_list,
+ unsigned int nr_clk);
+void samsung_clk_register_gate(void __iomem *base,
+ const struct samsung_gate_clock *clk_list,
+ unsigned int nr_clk);
+void samsung_clk_register_pll(void __iomem *base,
+ const struct samsung_pll_clock *clk_list,
+ unsigned int nr_clk);
+
+void samsung_cmu_register_clocks(void __iomem *base,
+ const struct samsung_clk_group *clk_groups,
+ unsigned int nr_groups);
+int samsung_cmu_register_one(struct udevice *dev,
+ const struct samsung_clk_group *clk_groups,
+ unsigned int nr_groups);
+
+/**
+ * samsung_register_cmu - Register CMU clocks ensuring parent CMU is present
+ * @dev: CMU device
+ * @clk_groups: list of CMU clock groups
+ * @parent_drv: name of parent CMU driver
+ *
+ * Register provided CMU clocks, but make sure CMU_TOP driver is instantiated
+ * first.
+ *
+ * Return: 0 on success or negative value on error.
+ */
+#define samsung_register_cmu(dev, clk_groups, parent_drv) \
+({ \
+ struct udevice *__parent; \
+ int __ret; \
+ \
+ __ret = uclass_get_device_by_driver(UCLASS_CLK, \
+ DM_DRIVER_GET(parent_drv), &__parent); \
+ if (__ret || !__parent) \
+ __ret = -ENOENT; \
+ else \
+ __ret = samsung_cmu_register_one(dev, clk_groups, \
+ ARRAY_SIZE(clk_groups)); \
+ __ret; \
+})
+
+#endif /* __EXYNOS_CLK_H */
diff --git a/drivers/pinctrl/exynos/Kconfig b/drivers/pinctrl/exynos/Kconfig
index a60f498..1b7fb62 100644
--- a/drivers/pinctrl/exynos/Kconfig
+++ b/drivers/pinctrl/exynos/Kconfig
@@ -16,3 +16,11 @@ config PINCTRL_EXYNOS78x0
help
Support pin multiplexing and pin configuration control on
Samsung's Exynos78x0 SoC.
+
+config PINCTRL_EXYNOS850
+ bool "Samsung Exynos850 pinctrl driver"
+ depends on ARCH_EXYNOS && PINCTRL_FULL
+ select PINCTRL_EXYNOS
+ help
+ Support pin multiplexing and pin configuration control on
+ Samsung's Exynos850 SoC.
diff --git a/drivers/pinctrl/exynos/Makefile b/drivers/pinctrl/exynos/Makefile
index 07db970..3abe122 100644
--- a/drivers/pinctrl/exynos/Makefile
+++ b/drivers/pinctrl/exynos/Makefile
@@ -6,3 +6,4 @@
obj-$(CONFIG_PINCTRL_EXYNOS) += pinctrl-exynos.o
obj-$(CONFIG_PINCTRL_EXYNOS7420) += pinctrl-exynos7420.o
obj-$(CONFIG_PINCTRL_EXYNOS78x0) += pinctrl-exynos78x0.o
+obj-$(CONFIG_PINCTRL_EXYNOS850) += pinctrl-exynos850.o
diff --git a/drivers/pinctrl/exynos/pinctrl-exynos850.c b/drivers/pinctrl/exynos/pinctrl-exynos850.c
new file mode 100644
index 0000000..3ec2636
--- /dev/null
+++ b/drivers/pinctrl/exynos/pinctrl-exynos850.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Exynos850 pinctrl driver.
+ */
+
+#include <dm.h>
+#include <dm/pinctrl.h>
+#include "pinctrl-exynos.h"
+
+#define EXYNOS850_PIN_BANK(pins, reg, id) \
+ { \
+ .type = &exynos850_bank_type, \
+ .offset = reg, \
+ .nr_pins = pins, \
+ .name = id \
+ }
+
+/* CON, DAT, PUD, DRV */
+static const struct samsung_pin_bank_type exynos850_bank_type = {
+ .fld_width = { 4, 1, 4, 4, },
+ .reg_offset = { 0x00, 0x04, 0x08, 0x0c, },
+};
+
+static const struct pinctrl_ops exynos850_pinctrl_ops = {
+ .set_state = exynos_pinctrl_set_state
+};
+
+/* pin banks of exynos850 pin-controller 0 (ALIVE) */
+static const struct samsung_pin_bank_data exynos850_pin_banks0[] = {
+ EXYNOS850_PIN_BANK(8, 0x000, "gpa0"),
+ EXYNOS850_PIN_BANK(8, 0x020, "gpa1"),
+ EXYNOS850_PIN_BANK(8, 0x040, "gpa2"),
+ EXYNOS850_PIN_BANK(8, 0x060, "gpa3"),
+ EXYNOS850_PIN_BANK(4, 0x080, "gpa4"),
+ EXYNOS850_PIN_BANK(3, 0x0a0, "gpq0"),
+};
+
+/* pin banks of exynos850 pin-controller 1 (CMGP) */
+static const struct samsung_pin_bank_data exynos850_pin_banks1[] = {
+ EXYNOS850_PIN_BANK(1, 0x000, "gpm0"),
+ EXYNOS850_PIN_BANK(1, 0x020, "gpm1"),
+ EXYNOS850_PIN_BANK(1, 0x040, "gpm2"),
+ EXYNOS850_PIN_BANK(1, 0x060, "gpm3"),
+ EXYNOS850_PIN_BANK(1, 0x080, "gpm4"),
+ EXYNOS850_PIN_BANK(1, 0x0a0, "gpm5"),
+ EXYNOS850_PIN_BANK(1, 0x0c0, "gpm6"),
+ EXYNOS850_PIN_BANK(1, 0x0e0, "gpm7"),
+};
+
+/* pin banks of exynos850 pin-controller 2 (AUD) */
+static const struct samsung_pin_bank_data exynos850_pin_banks2[] = {
+ EXYNOS850_PIN_BANK(5, 0x000, "gpb0"),
+ EXYNOS850_PIN_BANK(5, 0x020, "gpb1"),
+};
+
+/* pin banks of exynos850 pin-controller 3 (HSI) */
+static const struct samsung_pin_bank_data exynos850_pin_banks3[] = {
+ EXYNOS850_PIN_BANK(6, 0x000, "gpf2"),
+};
+
+/* pin banks of exynos850 pin-controller 4 (CORE) */
+static const struct samsung_pin_bank_data exynos850_pin_banks4[] = {
+ EXYNOS850_PIN_BANK(4, 0x000, "gpf0"),
+ EXYNOS850_PIN_BANK(8, 0x020, "gpf1"),
+};
+
+/* pin banks of exynos850 pin-controller 5 (PERI) */
+static const struct samsung_pin_bank_data exynos850_pin_banks5[] = {
+ EXYNOS850_PIN_BANK(2, 0x000, "gpg0"),
+ EXYNOS850_PIN_BANK(6, 0x020, "gpp0"),
+ EXYNOS850_PIN_BANK(4, 0x040, "gpp1"),
+ EXYNOS850_PIN_BANK(4, 0x060, "gpp2"),
+ EXYNOS850_PIN_BANK(8, 0x080, "gpg1"),
+ EXYNOS850_PIN_BANK(8, 0x0a0, "gpg2"),
+ EXYNOS850_PIN_BANK(1, 0x0c0, "gpg3"),
+ EXYNOS850_PIN_BANK(3, 0x0e0, "gpc0"),
+ EXYNOS850_PIN_BANK(6, 0x100, "gpc1"),
+};
+
+static const struct samsung_pin_ctrl exynos850_pin_ctrl[] = {
+ {
+ /* pin-controller instance 0 ALIVE data */
+ .pin_banks = exynos850_pin_banks0,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks0),
+ }, {
+ /* pin-controller instance 1 CMGP data */
+ .pin_banks = exynos850_pin_banks1,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks1),
+ }, {
+ /* pin-controller instance 2 AUD data */
+ .pin_banks = exynos850_pin_banks2,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks2),
+ }, {
+ /* pin-controller instance 3 HSI data */
+ .pin_banks = exynos850_pin_banks3,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks3),
+ }, {
+ /* pin-controller instance 4 CORE data */
+ .pin_banks = exynos850_pin_banks4,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks4),
+ }, {
+ /* pin-controller instance 5 PERI data */
+ .pin_banks = exynos850_pin_banks5,
+ .nr_banks = ARRAY_SIZE(exynos850_pin_banks5),
+ },
+ {/* list terminator */}
+};
+
+static const struct udevice_id exynos850_pinctrl_ids[] = {
+ { .compatible = "samsung,exynos850-pinctrl",
+ .data = (ulong)exynos850_pin_ctrl },
+ { }
+};
+
+U_BOOT_DRIVER(pinctrl_exynos850) = {
+ .name = "pinctrl_exynos850",
+ .id = UCLASS_PINCTRL,
+ .of_match = exynos850_pinctrl_ids,
+ .priv_auto = sizeof(struct exynos_pinctrl_priv),
+ .ops = &exynos850_pinctrl_ops,
+ .probe = exynos_pinctrl_probe,
+};
diff --git a/drivers/serial/serial_s5p.c b/drivers/serial/serial_s5p.c
index 7d04dcf..801b764 100644
--- a/drivers/serial/serial_s5p.c
+++ b/drivers/serial/serial_s5p.c
@@ -257,6 +257,7 @@ static const struct dm_serial_ops s5p_serial_ops = {
static const struct udevice_id s5p_serial_ids[] = {
{ .compatible = "samsung,exynos4210-uart", .data = PORT_S5P },
+ { .compatible = "samsung,exynos850-uart", .data = PORT_S5P },
{ .compatible = "apple,s5l-uart", .data = PORT_S5L },
{ }
};
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 85dac9d..03433bc0 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -40,6 +40,7 @@ config SOC_XILINX_VERSAL_NET
This allows other drivers to verify the SoC familiy & revision using
matching SoC attributes.
+source "drivers/soc/samsung/Kconfig"
source "drivers/soc/ti/Kconfig"
endmenu
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 8438565..610bf81 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -2,6 +2,7 @@
#
# Makefile for the U-Boot SOC specific device drivers.
+obj-$(CONFIG_SOC_SAMSUNG) += samsung/
obj-$(CONFIG_SOC_TI) += ti/
obj-$(CONFIG_SOC_DEVICE) += soc-uclass.o
obj-$(CONFIG_SOC_DEVICE_TI_K3) += soc_ti_k3.o
diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig
new file mode 100644
index 0000000..737b7ca
--- /dev/null
+++ b/drivers/soc/samsung/Kconfig
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+menuconfig SOC_SAMSUNG
+ bool "Samsung SoC drivers support"
+
+if SOC_SAMSUNG
+
+config EXYNOS_PMU
+ bool "Exynos PMU controller driver"
+ depends on ARCH_EXYNOS
+ select REGMAP
+ select SYSCON
+ help
+ Enable support for system controller configuration driver. It allows
+ one to configure system controller registers (e.g. some register in
+ PMU syscon) by providing register's offset, mask and value.
+
+config EXYNOS_USI
+ bool "Exynos USI (Universal Serial Interface) driver"
+ depends on ARCH_EXYNOS
+ select MISC
+ select REGMAP
+ select SYSCON
+ help
+ Enable support for USI block. USI (Universal Serial Interface) is an
+ IP-core found in modern Samsung Exynos SoCs, like Exynos850 and
+ ExynosAutoV9. USI block can be configured to provide one of the
+ following serial protocols: UART, SPI or High Speed I2C.
+
+ This driver allows one to configure USI for desired protocol, which
+ is usually done in USI node in Device Tree.
+
+endif
diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile
new file mode 100644
index 0000000..0eb3ed8
--- /dev/null
+++ b/drivers/soc/samsung/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o
+obj-$(CONFIG_EXYNOS_USI) += exynos-usi.o
diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c
new file mode 100644
index 0000000..233ad4a
--- /dev/null
+++ b/drivers/soc/samsung/exynos-pmu.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Exynos PMU (Power Management Unit) driver.
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+
+#define EXYNOS850_UART_IO_SHARE_CTRL 0x0760
+#define SEL_RXD_AP_UART_SHIFT 16
+#define SEL_RXD_AP_UART_MASK GENMASK(17, 16)
+#define SEL_TXD_GPIO_1_SHIFT 20
+#define SEL_TXD_GPIO_1_MASK GENMASK(21, 20)
+#define RXD_GPIO_1 0x3
+#define TXD_AP_UART 0x0
+
+struct exynos_pmu {
+ struct udevice *dev;
+ const struct exynos_pmu_data *pmu_data;
+ struct regmap *regmap;
+};
+
+struct exynos_pmu_data {
+ int (*pmu_init)(struct exynos_pmu *priv);
+};
+
+static int exynos850_pmu_init(struct exynos_pmu *priv)
+{
+ ofnode node;
+ bool uart_debug_1;
+ unsigned int offset, mask, value;
+
+ node = dev_ofnode(priv->dev);
+ uart_debug_1 = ofnode_read_bool(node, "samsung,uart-debug-1");
+ if (!uart_debug_1)
+ return 0;
+
+ /*
+ * If uart1_pins are used for serial, AP UART lines have to be muxed
+ * in PMU block to UART_DEBUG_1 path (GPIO_1). By default (reset value)
+ * UART_DEBUG_0 path (uart0_pins) is connected to AP UART lines.
+ */
+ offset = EXYNOS850_UART_IO_SHARE_CTRL;
+ mask = SEL_RXD_AP_UART_MASK | SEL_TXD_GPIO_1_MASK;
+ value = RXD_GPIO_1 << SEL_RXD_AP_UART_SHIFT |
+ TXD_AP_UART << SEL_TXD_GPIO_1_SHIFT;
+ return regmap_update_bits(priv->regmap, offset, mask, value);
+}
+
+static const struct exynos_pmu_data exynos850_pmu_data = {
+ .pmu_init = exynos850_pmu_init,
+};
+
+static int exynos_pmu_bind(struct udevice *dev)
+{
+ dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+ return 0;
+}
+
+static int exynos_pmu_probe(struct udevice *dev)
+{
+ ofnode node;
+ struct exynos_pmu *priv;
+
+ priv = dev_get_priv(dev);
+ priv->dev = dev;
+
+ node = dev_ofnode(dev);
+ priv->regmap = syscon_node_to_regmap(node);
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->pmu_data = (struct exynos_pmu_data *)dev_get_driver_data(dev);
+ if (priv->pmu_data && priv->pmu_data->pmu_init)
+ return priv->pmu_data->pmu_init(priv);
+
+ return 0;
+}
+
+static const struct udevice_id exynos_pmu_ids[] = {
+ {
+ .compatible = "samsung,exynos850-pmu",
+ .data = (ulong)&exynos850_pmu_data
+ },
+ { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(exynos_pmu) = {
+ .name = "exynos-pmu",
+ .id = UCLASS_NOP,
+ .of_match = exynos_pmu_ids,
+ .bind = exynos_pmu_bind,
+ .probe = exynos_pmu_probe,
+ .priv_auto = sizeof(struct exynos_pmu),
+};
diff --git a/drivers/soc/samsung/exynos-usi.c b/drivers/soc/samsung/exynos-usi.c
new file mode 100644
index 0000000..b746a78
--- /dev/null
+++ b/drivers/soc/samsung/exynos-usi.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2023 Linaro Ltd.
+ * Author: Sam Protsenko <semen.protsenko@linaro.org>
+ *
+ * Samsung Exynos USI driver (Universal Serial Interface).
+ */
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <errno.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#include <dt-bindings/soc/samsung,exynos-usi.h>
+
+/* USIv2: System Register: SW_CONF register bits */
+#define USI_V2_SW_CONF_NONE 0x0
+#define USI_V2_SW_CONF_UART BIT(0)
+#define USI_V2_SW_CONF_SPI BIT(1)
+#define USI_V2_SW_CONF_I2C BIT(2)
+#define USI_V2_SW_CONF_MASK (USI_V2_SW_CONF_UART | USI_V2_SW_CONF_SPI | \
+ USI_V2_SW_CONF_I2C)
+
+/* USIv2: USI register offsets */
+#define USI_CON 0x04
+#define USI_OPTION 0x08
+
+/* USIv2: USI register bits */
+#define USI_CON_RESET BIT(0)
+#define USI_OPTION_CLKREQ_ON BIT(1)
+#define USI_OPTION_CLKSTOP_ON BIT(2)
+
+enum exynos_usi_ver {
+ USI_VER2 = 2,
+};
+
+struct exynos_usi_variant {
+ enum exynos_usi_ver ver; /* USI IP-core version */
+ unsigned int sw_conf_mask; /* SW_CONF mask for all protocols */
+ size_t min_mode; /* first index in exynos_usi_modes[] */
+ size_t max_mode; /* last index in exynos_usi_modes[] */
+};
+
+struct exynos_usi {
+ void __iomem *regs; /* USI register map */
+
+ size_t mode; /* current USI SW_CONF mode index */
+ bool clkreq_on; /* always provide clock to IP */
+
+ /* System Register */
+ struct regmap *sysreg; /* System Register map */
+ unsigned int sw_conf; /* SW_CONF register offset in sysreg */
+
+ const struct exynos_usi_variant *data;
+};
+
+struct exynos_usi_mode {
+ const char *name; /* mode name */
+ unsigned int val; /* mode register value */
+};
+
+static const struct exynos_usi_mode exynos_usi_modes[] = {
+ [USI_V2_NONE] = { .name = "none", .val = USI_V2_SW_CONF_NONE },
+ [USI_V2_UART] = { .name = "uart", .val = USI_V2_SW_CONF_UART },
+ [USI_V2_SPI] = { .name = "spi", .val = USI_V2_SW_CONF_SPI },
+ [USI_V2_I2C] = { .name = "i2c", .val = USI_V2_SW_CONF_I2C },
+};
+
+static const struct exynos_usi_variant exynos850_usi_data = {
+ .ver = USI_VER2,
+ .sw_conf_mask = USI_V2_SW_CONF_MASK,
+ .min_mode = USI_V2_NONE,
+ .max_mode = USI_V2_I2C,
+};
+
+static const struct udevice_id exynos_usi_ids[] = {
+ {
+ .compatible = "samsung,exynos850-usi",
+ .data = (ulong)&exynos850_usi_data,
+ },
+ { } /* sentinel */
+};
+
+/**
+ * exynos_usi_set_sw_conf - Set USI block configuration mode
+ * @dev: Driver object
+ *
+ * Select underlying serial protocol (UART/SPI/I2C) in USI IP-core as specified
+ * in @usi.mode.
+ *
+ * Return: 0 on success, or negative error code on failure.
+ */
+static int exynos_usi_set_sw_conf(struct udevice *dev)
+{
+ struct exynos_usi *usi = dev_get_priv(dev);
+ size_t mode = usi->mode;
+ unsigned int val;
+ int ret;
+
+ if (mode < usi->data->min_mode || mode > usi->data->max_mode)
+ return -EINVAL;
+
+ val = exynos_usi_modes[mode].val;
+ ret = regmap_update_bits(usi->sysreg, usi->sw_conf,
+ usi->data->sw_conf_mask, val);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "protocol: %s\n", exynos_usi_modes[mode].name);
+
+ return 0;
+}
+
+/**
+ * exynos_usi_enable - Initialize USI block
+ * @usi: USI driver object
+ *
+ * USI IP-core start state is "reset" (on startup and after CPU resume). This
+ * routine enables the USI block by clearing the reset flag. It also configures
+ * HWACG behavior (needed e.g. for UART Rx). It should be performed before
+ * underlying protocol becomes functional.
+ */
+static void exynos_usi_enable(const struct exynos_usi *usi)
+{
+ u32 val;
+
+ /* Enable USI block */
+ val = readl(usi->regs + USI_CON);
+ val &= ~USI_CON_RESET;
+ writel(val, usi->regs + USI_CON);
+ udelay(1);
+
+ /* Continuously provide the clock to USI IP w/o gating */
+ if (usi->clkreq_on) {
+ val = readl(usi->regs + USI_OPTION);
+ val &= ~USI_OPTION_CLKSTOP_ON;
+ val |= USI_OPTION_CLKREQ_ON;
+ writel(val, usi->regs + USI_OPTION);
+ }
+}
+
+static int exynos_usi_configure(struct udevice *dev)
+{
+ struct exynos_usi *usi = dev_get_priv(dev);
+ int ret;
+
+ ret = exynos_usi_set_sw_conf(dev);
+ if (ret)
+ return ret;
+
+ if (usi->data->ver == USI_VER2)
+ exynos_usi_enable(usi);
+
+ return 0;
+}
+
+static int exynos_usi_of_to_plat(struct udevice *dev)
+{
+ struct exynos_usi *usi = dev_get_priv(dev);
+ ofnode node = dev_ofnode(dev);
+ int ret;
+ u32 mode;
+
+ usi->data = (struct exynos_usi_variant *)dev_get_driver_data(dev);
+ if (usi->data->ver == USI_VER2) {
+ usi->regs = dev_read_addr_ptr(dev);
+ if (!usi->regs)
+ return -ENODEV;
+ }
+
+ ret = ofnode_read_u32(node, "samsung,mode", &mode);
+ if (ret)
+ return ret;
+ if (mode < usi->data->min_mode || mode > usi->data->max_mode)
+ return -EINVAL;
+ usi->mode = mode;
+
+ usi->sysreg = syscon_regmap_lookup_by_phandle(dev, "samsung,sysreg");
+ if (IS_ERR(usi->sysreg))
+ return PTR_ERR(usi->sysreg);
+
+ ret = ofnode_read_u32_index(node, "samsung,sysreg", 1, &usi->sw_conf);
+ if (ret)
+ return ret;
+
+ usi->clkreq_on = ofnode_read_bool(node, "samsung,clkreq-on");
+
+ return 0;
+}
+
+static int exynos_usi_probe(struct udevice *dev)
+{
+ return exynos_usi_configure(dev);
+}
+
+U_BOOT_DRIVER(exynos_usi) = {
+ .name = "exynos-usi",
+ .id = UCLASS_MISC,
+ .of_match = exynos_usi_ids,
+ .of_to_plat = exynos_usi_of_to_plat,
+ .probe = exynos_usi_probe,
+ .priv_auto = sizeof(struct exynos_usi),
+};