@@ -5,6 +5,16 @@ menuconfig SOC_SAMSUNG
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
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0+
+obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o
obj-$(CONFIG_EXYNOS_USI) += exynos-usi.o
new file mode 100644
@@ -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),
+};