pinctrl: Add pinctrl driver support for Exynos7420 SoC

Add pinctrl driver support for Samsung's Exynos7420 SoC. The changes
have been split into Exynos7420 specific and common Exynos specific
portions so that this implementation is reusable on other Exynos
SoCs as well.

The Exynos pinctrl driver supports only device tree based pin
configuration. The bindings used are similar to the ones used in the
linux kernel.

Cc: Masahiro Yamada <yamada.masahiro@socionext.com>
Cc: Simon Glass <sjg@chromium.org>
Cc: Minkyu Kang <mk7.kang@samsung.com>
Signed-off-by: Thomas Abraham <thomas.ab@samsung.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Acked-by: Minkyu Kang <mk7.kang@samsung.com>
Signed-off-by: Minkyu Kang <mk7.kang@samsung.com>
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index 567b766..1785e3b 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -163,5 +163,6 @@
 
 source "drivers/pinctrl/nxp/Kconfig"
 source "drivers/pinctrl/uniphier/Kconfig"
+source "drivers/pinctrl/exynos/Kconfig"
 
 endmenu
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index b99ed2f..7f94681 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -12,3 +12,4 @@
 
 obj-$(CONFIG_PINCTRL_UNIPHIER)	+= uniphier/
 obj-$(CONFIG_PIC32_PINCTRL)	+= pinctrl_pic32.o
+obj-$(CONFIG_PINCTRL_EXYNOS)	+= exynos/
diff --git a/drivers/pinctrl/exynos/Kconfig b/drivers/pinctrl/exynos/Kconfig
new file mode 100644
index 0000000..84b6aaa
--- /dev/null
+++ b/drivers/pinctrl/exynos/Kconfig
@@ -0,0 +1,10 @@
+config PINCTRL_EXYNOS
+	bool
+
+config PINCTRL_EXYNOS7420
+	bool "Samsung Exynos7420 pinctrl driver"
+	depends on ARCH_EXYNOS && PINCTRL_FULL
+	select PINCTRL_EXYNOS
+	help
+	  Support pin multiplexing and pin configuration control on
+	  Samsung's Exynos7420 SoC.
diff --git a/drivers/pinctrl/exynos/Makefile b/drivers/pinctrl/exynos/Makefile
new file mode 100644
index 0000000..d9b941a
--- /dev/null
+++ b/drivers/pinctrl/exynos/Makefile
@@ -0,0 +1,9 @@
+#
+# Copyright (C) 2016 Samsung Electronics
+# Thomas Abraham <thomas.ab@samsung.com>
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+obj-$(CONFIG_PINCTRL_EXYNOS)		+= pinctrl-exynos.o
+obj-$(CONFIG_PINCTRL_EXYNOS7420)	+= pinctrl-exynos7420.o
diff --git a/drivers/pinctrl/exynos/pinctrl-exynos.c b/drivers/pinctrl/exynos/pinctrl-exynos.c
new file mode 100644
index 0000000..a28405f
--- /dev/null
+++ b/drivers/pinctrl/exynos/pinctrl-exynos.c
@@ -0,0 +1,141 @@
+/*
+ * Exynos pinctrl driver common code.
+ * Copyright (C) 2016 Samsung Electronics
+ * Thomas Abraham <thomas.ab@samsung.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <asm/io.h>
+#include "pinctrl-exynos.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * exynos_pinctrl_setup_peri: setup pinctrl for a peripheral.
+ * conf: soc specific pin configuration data array
+ * num_conf: number of configurations in the conf array.
+ * base: base address of the pin controller.
+ */
+void exynos_pinctrl_setup_peri(struct exynos_pinctrl_config_data *conf,
+		unsigned int num_conf, unsigned long base)
+{
+	unsigned int idx, val;
+
+	for (idx = 0; idx < num_conf; idx++) {
+		val = readl(base + conf[idx].offset);
+		val &= ~(conf[idx].mask);
+		val |= conf[idx].value;
+		writel(val, base + conf[idx].offset);
+	}
+}
+
+/* given a pin-name, return the address of pin config registers */
+static unsigned long pin_to_bank_base(struct udevice *dev, const char *pin_name,
+						u32 *pin)
+{
+	struct exynos_pinctrl_priv *priv = dev_get_priv(dev);
+	const struct samsung_pin_ctrl *pin_ctrl = priv->pin_ctrl;
+	const struct samsung_pin_bank_data *bank_data = pin_ctrl->pin_banks;
+	u32 nr_banks = pin_ctrl->nr_banks, idx = 0;
+	char bank[10];
+
+	/*
+	 * The format of the pin name is <bank name>-<pin_number>.
+	 * Example: gpa0-4 (gpa0 is the bank name and 4 is the pin number.
+	 */
+	while (pin_name[idx] != '-') {
+		bank[idx] = pin_name[idx];
+		idx++;
+	}
+	bank[idx] = '\0';
+	*pin = pin_name[++idx] - '0';
+
+	/* lookup the pin bank data using the pin bank name */
+	for (idx = 0; idx < nr_banks; idx++)
+		if (!strcmp(bank, bank_data[idx].name))
+			break;
+
+	return priv->base + bank_data[idx].offset;
+}
+
+/**
+ * exynos_pinctrl_set_state: configure a pin state.
+ * dev: the pinctrl device to be configured.
+ * config: the state to be configured.
+ */
+int exynos_pinctrl_set_state(struct udevice *dev, struct udevice *config)
+{
+	const void *fdt = gd->fdt_blob;
+	int node = config->of_offset;
+	unsigned int count, idx, pin_num, ret;
+	unsigned int pinfunc, pinpud, pindrv;
+	unsigned long reg, value;
+	const char *name;
+
+	/*
+	 * refer to the following document for the pinctrl bindings
+	 * linux/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
+	 */
+	count = fdt_count_strings(fdt, node, "samsung,pins");
+	if (count <= 0)
+		return -EINVAL;
+
+	pinfunc = fdtdec_get_int(fdt, node, "samsung,pin-function", -1);
+	pinpud = fdtdec_get_int(fdt, node, "samsung,pin-pud", -1);
+	pindrv = fdtdec_get_int(fdt, node, "samsung,pin-drv", -1);
+
+	for (idx = 0; idx < count; idx++) {
+		ret = fdt_get_string_index(fdt, node, "samsung,pins",
+						idx, &name);
+		if (ret < 0)
+			continue;
+		reg = pin_to_bank_base(dev, name, &pin_num);
+
+		if (pinfunc != -1) {
+			value = readl(reg + PIN_CON);
+			value &= ~(0xf << (pin_num << 2));
+			value |= (pinfunc << (pin_num << 2));
+			writel(value, reg + PIN_CON);
+		}
+
+		if (pinpud != -1) {
+			value = readl(reg + PIN_PUD);
+			value &= ~(0x3 << (pin_num << 1));
+			value |= (pinpud << (pin_num << 1));
+			writel(value, reg + PIN_PUD);
+		}
+
+		if (pindrv != -1) {
+			value = readl(reg + PIN_DRV);
+			value &= ~(0x3 << (pin_num << 1));
+			value |= (pindrv << (pin_num << 1));
+			writel(value, reg + PIN_DRV);
+		}
+	}
+
+	return 0;
+}
+
+int exynos_pinctrl_probe(struct udevice *dev)
+{
+	struct exynos_pinctrl_priv *priv;
+	fdt_addr_t base;
+
+	priv = dev_get_priv(dev);
+	if (!priv)
+		return -EINVAL;
+
+	base = dev_get_addr(dev);
+	if (base == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	priv->base = base;
+	priv->pin_ctrl = (struct samsung_pin_ctrl *)dev_get_driver_data(dev) +
+				dev->req_seq;
+
+	return 0;
+}
diff --git a/drivers/pinctrl/exynos/pinctrl-exynos.h b/drivers/pinctrl/exynos/pinctrl-exynos.h
new file mode 100644
index 0000000..abd582d
--- /dev/null
+++ b/drivers/pinctrl/exynos/pinctrl-exynos.h
@@ -0,0 +1,77 @@
+/*
+ * Exynos pinctrl driver header.
+ * Copyright (C) 2016 Samsung Electronics
+ * Thomas Abraham <thomas.ab@samsung.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef __PINCTRL_EXYNOS_H_
+#define __PINCTRL_EXYNOS__H_
+
+#define PIN_CON		0x00	/* Offset of pin function register */
+#define PIN_DAT		0x04	/* Offset of pin data register */
+#define PIN_PUD		0x08	/* Offset of pin pull up/down config register */
+#define PIN_DRV		0x0C	/* Offset of pin drive strength register */
+
+/**
+ * struct samsung_pin_bank_data: represent a controller pin-bank data.
+ * @offset: starting offset of the pin-bank registers.
+ * @nr_pins: number of pins included in this bank.
+ * @name: name to be prefixed for each pin in this pin bank.
+ */
+struct samsung_pin_bank_data {
+	u32		offset;
+	u8		nr_pins;
+	const char	*name;
+};
+
+#define EXYNOS_PIN_BANK(pins, reg, id)			\
+	{						\
+		.offset	= reg,				\
+		.nr_pins	= pins,			\
+		.name		= id			\
+	}
+
+/**
+ * struct samsung_pin_ctrl: represent a pin controller.
+ * @pin_banks: list of pin banks included in this controller.
+ * @nr_banks: number of pin banks.
+ */
+struct samsung_pin_ctrl {
+	const struct samsung_pin_bank_data *pin_banks;
+	u32 nr_banks;
+};
+
+/**
+ * struct exynos_pinctrl_priv: exynos pin controller driver private data
+ * @pin_ctrl: pin controller bank information.
+ * @base: base address of the pin controller instance.
+ * @num_banks: number of pin banks included in the pin controller.
+ */
+struct exynos_pinctrl_priv {
+	const struct samsung_pin_ctrl *pin_ctrl;
+	unsigned long base;
+	int num_banks;
+};
+
+/**
+ * struct exynos_pinctrl_config_data: configuration for a peripheral.
+ * @offset: offset of the config registers in the controller.
+ * @mask: value of the register to be masked with.
+ * @value: new value to be programmed.
+ */
+struct exynos_pinctrl_config_data {
+	const unsigned int	offset;
+	const unsigned int	mask;
+	const unsigned int	value;
+};
+
+
+void exynos_pinctrl_setup_peri(struct exynos_pinctrl_config_data *conf,
+		unsigned int num_conf, unsigned long base);
+int exynos_pinctrl_set_state(struct udevice *dev,
+		struct udevice *config);
+int exynos_pinctrl_probe(struct udevice *dev);
+
+#endif /* __PINCTRL_EXYNOS_H_ */
diff --git a/drivers/pinctrl/exynos/pinctrl-exynos7420.c b/drivers/pinctrl/exynos/pinctrl-exynos7420.c
new file mode 100644
index 0000000..8ae5ce7
--- /dev/null
+++ b/drivers/pinctrl/exynos/pinctrl-exynos7420.c
@@ -0,0 +1,120 @@
+/*
+ * Exynos7420 pinctrl driver.
+ * Copyright (C) 2016 Samsung Electronics
+ * Thomas Abraham <thomas.ab@samsung.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <asm/io.h>
+#include <dm/pinctrl.h>
+#include <dm/root.h>
+#include <fdtdec.h>
+#include <asm/arch/pinmux.h>
+#include "pinctrl-exynos.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define	GPD1_OFFSET	0xc0
+
+static struct exynos_pinctrl_config_data serial2_conf[] = {
+	{
+		.offset	= GPD1_OFFSET + PIN_CON,
+		.mask	= 0x00ff0000,
+		.value	= 0x00220000,
+	}, {
+		.offset	= GPD1_OFFSET + PIN_PUD,
+		.mask	= 0x00000f00,
+		.value	= 0x00000f00,
+	},
+};
+
+static int exynos7420_pinctrl_request(struct udevice *dev, int peripheral,
+						int flags)
+{
+	struct exynos_pinctrl_priv *priv = dev_get_priv(dev);
+	unsigned long base = priv->base;
+
+	switch (PERIPH_ID_UART2) {
+	case PERIPH_ID_UART2:
+		exynos_pinctrl_setup_peri(serial2_conf,
+					  ARRAY_SIZE(serial2_conf), base);
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static struct pinctrl_ops exynos7420_pinctrl_ops = {
+	.set_state	= exynos_pinctrl_set_state,
+	.request	= exynos7420_pinctrl_request,
+};
+
+/* pin banks of Exynos7420 pin-controller - BUS0 */
+static const struct samsung_pin_bank_data exynos7420_pin_banks0[] = {
+	EXYNOS_PIN_BANK(5, 0x000, "gpb0"),
+	EXYNOS_PIN_BANK(8, 0x020, "gpc0"),
+	EXYNOS_PIN_BANK(2, 0x040, "gpc1"),
+	EXYNOS_PIN_BANK(6, 0x060, "gpc2"),
+	EXYNOS_PIN_BANK(8, 0x080, "gpc3"),
+	EXYNOS_PIN_BANK(4, 0x0a0, "gpd0"),
+	EXYNOS_PIN_BANK(6, 0x0c0, "gpd1"),
+	EXYNOS_PIN_BANK(8, 0x0e0, "gpd2"),
+	EXYNOS_PIN_BANK(5, 0x100, "gpd4"),
+	EXYNOS_PIN_BANK(4, 0x120, "gpd5"),
+	EXYNOS_PIN_BANK(6, 0x140, "gpd6"),
+	EXYNOS_PIN_BANK(3, 0x160, "gpd7"),
+	EXYNOS_PIN_BANK(2, 0x180, "gpd8"),
+	EXYNOS_PIN_BANK(2, 0x1a0, "gpg0"),
+	EXYNOS_PIN_BANK(4, 0x1c0, "gpg3"),
+};
+
+/* pin banks of Exynos7420 pin-controller - FSYS0 */
+static const struct samsung_pin_bank_data exynos7420_pin_banks1[] = {
+	EXYNOS_PIN_BANK(7, 0x000, "gpr4"),
+};
+
+/* pin banks of Exynos7420 pin-controller - FSYS1 */
+static const struct samsung_pin_bank_data exynos7420_pin_banks2[] = {
+	EXYNOS_PIN_BANK(4, 0x000, "gpr0"),
+	EXYNOS_PIN_BANK(8, 0x020, "gpr1"),
+	EXYNOS_PIN_BANK(5, 0x040, "gpr2"),
+	EXYNOS_PIN_BANK(8, 0x060, "gpr3"),
+};
+
+const struct samsung_pin_ctrl exynos7420_pin_ctrl[] = {
+	{
+		/* pin-controller instance BUS0 data */
+		.pin_banks	= exynos7420_pin_banks0,
+		.nr_banks	= ARRAY_SIZE(exynos7420_pin_banks0),
+	}, {
+		/* pin-controller instance FSYS0 data */
+		.pin_banks	= exynos7420_pin_banks1,
+		.nr_banks	= ARRAY_SIZE(exynos7420_pin_banks1),
+	}, {
+		/* pin-controller instance FSYS1 data */
+		.pin_banks	= exynos7420_pin_banks2,
+		.nr_banks	= ARRAY_SIZE(exynos7420_pin_banks2),
+	},
+};
+
+static const struct udevice_id exynos7420_pinctrl_ids[] = {
+	{ .compatible = "samsung,exynos7420-pinctrl",
+		.data = (ulong)exynos7420_pin_ctrl },
+	{ }
+};
+
+U_BOOT_DRIVER(pinctrl_exynos7420) = {
+	.name		= "pinctrl_exynos7420",
+	.id		= UCLASS_PINCTRL,
+	.of_match	= exynos7420_pinctrl_ids,
+	.priv_auto_alloc_size = sizeof(struct exynos_pinctrl_priv),
+	.ops		= &exynos7420_pinctrl_ops,
+	.probe		= exynos_pinctrl_probe,
+	.flags		= DM_FLAG_PRE_RELOC
+};