ARM: 6020/1: ST SPEAr: Adding gpio pad multiplexing support

GPIO Pads in spear platform are are multiplexed in various machines.
This patch adds support for this pad multiplexing.

Reviewed-by: Linus Walleij <linux.walleij@stericsson.com>
Signed-off-by: Viresh Kumar <viresh.kumar@st.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
diff --git a/arch/arm/plat-spear/Makefile b/arch/arm/plat-spear/Makefile
index 96f9ac3..6f4ad5e 100644
--- a/arch/arm/plat-spear/Makefile
+++ b/arch/arm/plat-spear/Makefile
@@ -3,4 +3,4 @@
 #
 
 # Common support
-obj-y	:= clock.o time.o
+obj-y	:= clock.o padmux.o time.o
diff --git a/arch/arm/plat-spear/include/plat/padmux.h b/arch/arm/plat-spear/include/plat/padmux.h
new file mode 100644
index 0000000..877f3adc
--- /dev/null
+++ b/arch/arm/plat-spear/include/plat/padmux.h
@@ -0,0 +1,92 @@
+/*
+ * arch/arm/plat-spear/include/plat/padmux.h
+ *
+ * SPEAr platform specific gpio pads muxing file
+ *
+ * Copyright (C) 2009 ST Microelectronics
+ * Viresh Kumar<viresh.kumar@st.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __PLAT_PADMUX_H
+#define __PLAT_PADMUX_H
+
+#include <linux/types.h>
+
+/*
+ * struct pmx_reg: configuration structure for mode reg and mux reg
+ *
+ * offset: offset of mode reg
+ * mask: mask of mode reg
+ */
+struct pmx_reg {
+	u32 offset;
+	u32 mask;
+};
+
+/*
+ * struct pmx_dev_mode: configuration structure every group of modes of a device
+ *
+ * ids: all modes for this configuration
+ * mask: mask for supported mode
+ */
+struct pmx_dev_mode {
+	u32 ids;
+	u32 mask;
+};
+
+/*
+ * struct pmx_mode: mode definition structure
+ *
+ * name: mode name
+ * mask: mode mask
+ */
+struct pmx_mode {
+	char *name;
+	u32 id;
+	u32 mask;
+};
+
+/*
+ * struct pmx_dev: device definition structure
+ *
+ * name: device name
+ * modes: device configuration array for different modes supported
+ * mode_count: size of modes array
+ * is_active: is peripheral active/enabled
+ * enb_on_reset: if 1, mask bits to be cleared in reg otherwise to be set in reg
+ */
+struct pmx_dev {
+	char *name;
+	struct pmx_dev_mode *modes;
+	u8 mode_count;
+	bool is_active;
+	bool enb_on_reset;
+};
+
+/*
+ * struct pmx_driver: driver definition structure
+ *
+ * mode: mode to be set
+ * devs: array of pointer to pmx devices
+ * devs_count: ARRAY_SIZE of devs
+ * base: base address of soc config registers
+ * mode_reg: structure of mode config register
+ * mux_reg: structure of device mux config register
+ */
+struct pmx_driver {
+	struct pmx_mode *mode;
+	struct pmx_dev **devs;
+	u8 devs_count;
+	u32 *base;
+	struct pmx_reg mode_reg;
+	struct pmx_reg mux_reg;
+};
+
+/* pmx functions */
+int pmx_register(struct pmx_driver *driver);
+
+#endif /* __PLAT_PADMUX_H */
diff --git a/arch/arm/plat-spear/padmux.c b/arch/arm/plat-spear/padmux.c
new file mode 100644
index 0000000..d2aab3a
--- /dev/null
+++ b/arch/arm/plat-spear/padmux.c
@@ -0,0 +1,164 @@
+/*
+ * arch/arm/plat-spear/include/plat/padmux.c
+ *
+ * SPEAr platform specific gpio pads muxing source file
+ *
+ * Copyright (C) 2009 ST Microelectronics
+ * Viresh Kumar<viresh.kumar@st.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <plat/padmux.h>
+
+/*
+ * struct pmx: pmx definition structure
+ *
+ * base: base address of configuration registers
+ * mode_reg: mode configurations
+ * mux_reg: muxing configurations
+ * active_mode: pointer to current active mode
+ */
+struct pmx {
+	u32 base;
+	struct pmx_reg mode_reg;
+	struct pmx_reg mux_reg;
+	struct pmx_mode *active_mode;
+};
+
+static struct pmx *pmx;
+
+/**
+ * pmx_mode_set - Enables an multiplexing mode
+ * @mode - pointer to pmx mode
+ *
+ * It will set mode of operation in hardware.
+ * Returns -ve on Err otherwise 0
+ */
+static int pmx_mode_set(struct pmx_mode *mode)
+{
+	u32 val;
+
+	if (!mode->name)
+		return -EFAULT;
+
+	pmx->active_mode = mode;
+
+	val = readl(pmx->base + pmx->mode_reg.offset);
+	val &= ~pmx->mode_reg.mask;
+	val |= mode->mask & pmx->mode_reg.mask;
+	writel(val, pmx->base + pmx->mode_reg.offset);
+
+	return 0;
+}
+
+/**
+ * pmx_devs_enable - Enables list of devices
+ * @devs - pointer to pmx device array
+ * @count - number of devices to enable
+ *
+ * It will enable pads for all required peripherals once and only once.
+ * If peripheral is not supported by current mode then request is rejected.
+ * Conflicts between peripherals are not handled and peripherals will be
+ * enabled in the order they are present in pmx_dev array.
+ * In case of conflicts last peripheral enalbed will be present.
+ * Returns -ve on Err otherwise 0
+ */
+static int pmx_devs_enable(struct pmx_dev **devs, u8 count)
+{
+	u32 val, i, mask;
+
+	if (!count)
+		return -EINVAL;
+
+	val = readl(pmx->base + pmx->mux_reg.offset);
+	for (i = 0; i < count; i++) {
+		u8 j = 0;
+
+		if (!devs[i]->name || !devs[i]->modes) {
+			printk(KERN_ERR "padmux: dev name or modes is null\n");
+			continue;
+		}
+		/* check if peripheral exists in active mode */
+		if (pmx->active_mode) {
+			bool found = false;
+			for (j = 0; j < devs[i]->mode_count; j++) {
+				if (devs[i]->modes[j].ids &
+						pmx->active_mode->id) {
+					found = true;
+					break;
+				}
+			}
+			if (found == false) {
+				printk(KERN_ERR "%s device not available in %s"\
+						"mode\n", devs[i]->name,
+						pmx->active_mode->name);
+				continue;
+			}
+		}
+
+		/* enable peripheral */
+		mask = devs[i]->modes[j].mask & pmx->mux_reg.mask;
+		if (devs[i]->enb_on_reset)
+			val &= ~mask;
+		else
+			val |= mask;
+
+		devs[i]->is_active = true;
+	}
+	writel(val, pmx->base + pmx->mux_reg.offset);
+	kfree(pmx);
+
+	/* this will ensure that multiplexing can't be changed now */
+	pmx = (struct pmx *)-1;
+
+	return 0;
+}
+
+/**
+ * pmx_register - registers a platform requesting pad mux feature
+ * @driver - pointer to driver structure containing driver specific parameters
+ *
+ * Also this must be called only once. This will allocate memory for pmx
+ * structure, will call pmx_mode_set, will call pmx_devs_enable.
+ * Returns -ve on Err otherwise 0
+ */
+int pmx_register(struct pmx_driver *driver)
+{
+	int ret = 0;
+
+	if (pmx)
+		return -EPERM;
+	if (!driver->base || !driver->devs)
+		return -EFAULT;
+
+	pmx = kzalloc(sizeof(*pmx), GFP_KERNEL);
+	if (!pmx)
+		return -ENOMEM;
+
+	pmx->base = (u32)driver->base;
+	pmx->mode_reg.offset = driver->mode_reg.offset;
+	pmx->mode_reg.mask = driver->mode_reg.mask;
+	pmx->mux_reg.offset = driver->mux_reg.offset;
+	pmx->mux_reg.mask = driver->mux_reg.mask;
+
+	/* choose mode to enable */
+	if (driver->mode) {
+		ret = pmx_mode_set(driver->mode);
+		if (ret)
+			goto pmx_fail;
+	}
+	ret = pmx_devs_enable(driver->devs, driver->devs_count);
+	if (ret)
+		goto pmx_fail;
+
+	return 0;
+
+pmx_fail:
+	return ret;
+}