soc: samsung: Add Exynos PMU driver

Add basic Power Management Unit (PMU) driver for Exynos SoCs. For now
it's only capable of changing UART path in PMU, which is needed for
E850-96 board. The driver's structure resembles the exynos-pmu driver
from Linux kernel, and although it's very basic and slim at the moment,
it can be easily extended in future if the need arises.

UCLASS_NOP is used, as there are no benefits in using more elaborate
classes like UCLASS_MISC in this case. The DM_FLAG_PROBE_AFTER_BIND flag
is added in bind function, as the probe function must be always called
for this driver.

Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
Reviewed-by: Chanho Park <chanho61.park@samsung.com>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig
index ffb87fe..737b7ca 100644
--- a/drivers/soc/samsung/Kconfig
+++ b/drivers/soc/samsung/Kconfig
@@ -5,6 +5,16 @@
 
 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
diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile
index 833ac07..0eb3ed8 100644
--- a/drivers/soc/samsung/Makefile
+++ b/drivers/soc/samsung/Makefile
@@ -1,3 +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),
+};