|
@@ -6,53 +6,362 @@
|
|
|
* Copyright (C) 2009 ST-Ericsson AB
|
|
|
* License terms: GNU General Public License (GPL) version 2
|
|
|
* U300 PADMUX functions
|
|
|
- * Author: Linus Walleij <linus.walleij@stericsson.com>
|
|
|
- *
|
|
|
+ * Author: Martin Persson <martin.persson@stericsson.com>
|
|
|
*/
|
|
|
-#include <linux/io.h>
|
|
|
+
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/device.h>
|
|
|
#include <linux/err.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <linux/io.h>
|
|
|
+#include <linux/mutex.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/bug.h>
|
|
|
+#include <linux/debugfs.h>
|
|
|
+#include <linux/seq_file.h>
|
|
|
#include <mach/u300-regs.h>
|
|
|
#include <mach/syscon.h>
|
|
|
-
|
|
|
#include "padmux.h"
|
|
|
|
|
|
-/* Set the PAD MUX to route the MMC reader correctly to GPIO0. */
|
|
|
-void pmx_set_mission_mode_mmc(void)
|
|
|
-{
|
|
|
- u16 val;
|
|
|
-
|
|
|
- val = readw(U300_SYSCON_VBASE + U300_SYSCON_PMC1LR);
|
|
|
- val &= ~U300_SYSCON_PMC1LR_MMCSD_MASK;
|
|
|
- writew(val, U300_SYSCON_VBASE + U300_SYSCON_PMC1LR);
|
|
|
- val = readw(U300_SYSCON_VBASE + U300_SYSCON_PMC1HR);
|
|
|
- val &= ~U300_SYSCON_PMC1HR_APP_GPIO_1_MASK;
|
|
|
- val |= U300_SYSCON_PMC1HR_APP_GPIO_1_MMC;
|
|
|
- writew(val, U300_SYSCON_VBASE + U300_SYSCON_PMC1HR);
|
|
|
-}
|
|
|
-
|
|
|
-void pmx_set_mission_mode_spi(void)
|
|
|
-{
|
|
|
- u16 val;
|
|
|
-
|
|
|
- /* Set up padmuxing so the SPI port and its chipselects are active */
|
|
|
- val = readw(U300_SYSCON_VBASE + U300_SYSCON_PMC1HR);
|
|
|
- /*
|
|
|
- * Activate the SPI port (disable the use of these pins for generic
|
|
|
- * GPIO, DSP, AAIF
|
|
|
- */
|
|
|
- val &= ~U300_SYSCON_PMC1HR_APP_SPI_2_MASK;
|
|
|
- val |= U300_SYSCON_PMC1HR_APP_SPI_2_SPI;
|
|
|
- /*
|
|
|
- * Use GPIO pin SPI CS1 for CS1 actually (it can be used for other
|
|
|
- * things also)
|
|
|
- */
|
|
|
- val &= ~U300_SYSCON_PMC1HR_APP_SPI_CS_1_MASK;
|
|
|
- val |= U300_SYSCON_PMC1HR_APP_SPI_CS_1_SPI;
|
|
|
- /*
|
|
|
- * Use GPIO pin SPI CS2 for CS2 actually (it can be used for other
|
|
|
- * things also)
|
|
|
- */
|
|
|
- val &= ~U300_SYSCON_PMC1HR_APP_SPI_CS_2_MASK;
|
|
|
- val |= U300_SYSCON_PMC1HR_APP_SPI_CS_2_SPI;
|
|
|
- writew(val, U300_SYSCON_VBASE + U300_SYSCON_PMC1HR);
|
|
|
+static DEFINE_MUTEX(pmx_mutex);
|
|
|
+
|
|
|
+const u32 pmx_registers[] = {
|
|
|
+ (U300_SYSCON_VBASE + U300_SYSCON_PMC1LR),
|
|
|
+ (U300_SYSCON_VBASE + U300_SYSCON_PMC1HR),
|
|
|
+ (U300_SYSCON_VBASE + U300_SYSCON_PMC2R),
|
|
|
+ (U300_SYSCON_VBASE + U300_SYSCON_PMC3R),
|
|
|
+ (U300_SYSCON_VBASE + U300_SYSCON_PMC4R)
|
|
|
+};
|
|
|
+
|
|
|
+/* High level functionality */
|
|
|
+
|
|
|
+/* Lazy dog:
|
|
|
+ * onmask = {
|
|
|
+ * {"PMC1LR" mask, "PMC1LR" value},
|
|
|
+ * {"PMC1HR" mask, "PMC1HR" value},
|
|
|
+ * {"PMC2R" mask, "PMC2R" value},
|
|
|
+ * {"PMC3R" mask, "PMC3R" value},
|
|
|
+ * {"PMC4R" mask, "PMC4R" value}
|
|
|
+ * }
|
|
|
+ */
|
|
|
+static struct pmx mmc_setting = {
|
|
|
+ .setting = U300_APP_PMX_MMC_SETTING,
|
|
|
+ .default_on = false,
|
|
|
+ .activated = false,
|
|
|
+ .name = "MMC",
|
|
|
+ .onmask = {
|
|
|
+ {U300_SYSCON_PMC1LR_MMCSD_MASK,
|
|
|
+ U300_SYSCON_PMC1LR_MMCSD_MMCSD},
|
|
|
+ {0, 0},
|
|
|
+ {0, 0},
|
|
|
+ {0, 0},
|
|
|
+ {U300_SYSCON_PMC4R_APP_MISC_12_MASK,
|
|
|
+ U300_SYSCON_PMC4R_APP_MISC_12_APP_GPIO}
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static struct pmx spi_setting = {
|
|
|
+ .setting = U300_APP_PMX_SPI_SETTING,
|
|
|
+ .default_on = false,
|
|
|
+ .activated = false,
|
|
|
+ .name = "SPI",
|
|
|
+ .onmask = {{0, 0},
|
|
|
+ {U300_SYSCON_PMC1HR_APP_SPI_2_MASK |
|
|
|
+ U300_SYSCON_PMC1HR_APP_SPI_CS_1_MASK |
|
|
|
+ U300_SYSCON_PMC1HR_APP_SPI_CS_2_MASK,
|
|
|
+ U300_SYSCON_PMC1HR_APP_SPI_2_SPI |
|
|
|
+ U300_SYSCON_PMC1HR_APP_SPI_CS_1_SPI |
|
|
|
+ U300_SYSCON_PMC1HR_APP_SPI_CS_2_SPI},
|
|
|
+ {0, 0},
|
|
|
+ {0, 0},
|
|
|
+ {0, 0}
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+/* Available padmux settings */
|
|
|
+static struct pmx *pmx_settings[] = {
|
|
|
+ &mmc_setting,
|
|
|
+ &spi_setting,
|
|
|
+};
|
|
|
+
|
|
|
+static void update_registers(struct pmx *pmx, bool activate)
|
|
|
+{
|
|
|
+ u16 regval, val, mask;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_registers); i++) {
|
|
|
+ if (activate)
|
|
|
+ val = pmx->onmask[i].val;
|
|
|
+ else
|
|
|
+ val = 0;
|
|
|
+
|
|
|
+ mask = pmx->onmask[i].mask;
|
|
|
+ if (mask != 0) {
|
|
|
+ regval = readw(pmx_registers[i]);
|
|
|
+ regval &= ~mask;
|
|
|
+ regval |= val;
|
|
|
+ writew(regval, pmx_registers[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+struct pmx *pmx_get(struct device *dev, enum pmx_settings setting)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ struct pmx *pmx = ERR_PTR(-ENOENT);
|
|
|
+
|
|
|
+ if (dev == NULL)
|
|
|
+ return ERR_PTR(-EINVAL);
|
|
|
+
|
|
|
+ mutex_lock(&pmx_mutex);
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+
|
|
|
+ if (setting == pmx_settings[i]->setting) {
|
|
|
+
|
|
|
+ if (pmx_settings[i]->dev != NULL) {
|
|
|
+ WARN(1, "padmux: required setting "
|
|
|
+ "in use by another consumer\n");
|
|
|
+ } else {
|
|
|
+ pmx = pmx_settings[i];
|
|
|
+ pmx->dev = dev;
|
|
|
+ dev_dbg(dev, "padmux: setting nr %d is now "
|
|
|
+ "bound to %s and ready to use\n",
|
|
|
+ setting, dev_name(dev));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mutex_unlock(&pmx_mutex);
|
|
|
+
|
|
|
+ return pmx;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(pmx_get);
|
|
|
+
|
|
|
+int pmx_put(struct device *dev, struct pmx *pmx)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ int ret = -ENOENT;
|
|
|
+
|
|
|
+ if (pmx == NULL || dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ mutex_lock(&pmx_mutex);
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+
|
|
|
+ if (pmx->setting == pmx_settings[i]->setting) {
|
|
|
+
|
|
|
+ if (dev != pmx->dev) {
|
|
|
+ WARN(1, "padmux: cannot release handle as "
|
|
|
+ "it is bound to another consumer\n");
|
|
|
+ ret = -EINVAL;
|
|
|
+ break;
|
|
|
+ } else {
|
|
|
+ pmx_settings[i]->dev = NULL;
|
|
|
+ ret = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mutex_unlock(&pmx_mutex);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(pmx_put);
|
|
|
+
|
|
|
+int pmx_activate(struct device *dev, struct pmx *pmx)
|
|
|
+{
|
|
|
+ int i, j, ret;
|
|
|
+ ret = 0;
|
|
|
+
|
|
|
+ if (pmx == NULL || dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ mutex_lock(&pmx_mutex);
|
|
|
+
|
|
|
+ /* Make sure the required bits are not used */
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+
|
|
|
+ if (pmx_settings[i]->dev == NULL || pmx_settings[i] == pmx)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ for (j = 0; j < ARRAY_SIZE(pmx_registers); j++) {
|
|
|
+
|
|
|
+ if (pmx_settings[i]->onmask[j].mask & pmx->
|
|
|
+ onmask[j].mask) {
|
|
|
+ /* More than one entry on the same bits */
|
|
|
+ WARN(1, "padmux: cannot activate "
|
|
|
+ "setting. Bit conflict with "
|
|
|
+ "an active setting\n");
|
|
|
+
|
|
|
+ ret = -EUSERS;
|
|
|
+ goto exit;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ update_registers(pmx, true);
|
|
|
+ pmx->activated = true;
|
|
|
+ dev_dbg(dev, "padmux: setting nr %d is activated\n",
|
|
|
+ pmx->setting);
|
|
|
+
|
|
|
+exit:
|
|
|
+ mutex_unlock(&pmx_mutex);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(pmx_activate);
|
|
|
+
|
|
|
+int pmx_deactivate(struct device *dev, struct pmx *pmx)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ int ret = -ENOENT;
|
|
|
+
|
|
|
+ if (pmx == NULL || dev == NULL)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ mutex_lock(&pmx_mutex);
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+
|
|
|
+ if (pmx_settings[i]->dev == NULL)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (pmx->setting == pmx_settings[i]->setting) {
|
|
|
+
|
|
|
+ if (dev != pmx->dev) {
|
|
|
+ WARN(1, "padmux: cannot deactivate "
|
|
|
+ "pmx setting as it was activated "
|
|
|
+ "by another consumer\n");
|
|
|
+
|
|
|
+ ret = -EBUSY;
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ update_registers(pmx, false);
|
|
|
+ pmx_settings[i]->dev = NULL;
|
|
|
+ pmx->activated = false;
|
|
|
+ ret = 0;
|
|
|
+ dev_dbg(dev, "padmux: setting nr %d is deactivated",
|
|
|
+ pmx->setting);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mutex_unlock(&pmx_mutex);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(pmx_deactivate);
|
|
|
+
|
|
|
+/*
|
|
|
+ * For internal use only. If it is to be exported,
|
|
|
+ * it should be reentrant. Notice that pmx_activate
|
|
|
+ * (i.e. runtime settings) always override default settings.
|
|
|
+ */
|
|
|
+static int pmx_set_default(void)
|
|
|
+{
|
|
|
+ /* Used to identify several entries on the same bits */
|
|
|
+ u16 modbits[ARRAY_SIZE(pmx_registers)];
|
|
|
+
|
|
|
+ int i, j;
|
|
|
+
|
|
|
+ memset(modbits, 0, ARRAY_SIZE(pmx_registers) * sizeof(u16));
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+
|
|
|
+ if (!pmx_settings[i]->default_on)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ for (j = 0; j < ARRAY_SIZE(pmx_registers); j++) {
|
|
|
+
|
|
|
+ /* Make sure there is only one entry on the same bits */
|
|
|
+ if (modbits[j] & pmx_settings[i]->onmask[j].mask) {
|
|
|
+ BUG();
|
|
|
+ return -EUSERS;
|
|
|
+ }
|
|
|
+ modbits[j] |= pmx_settings[i]->onmask[j].mask;
|
|
|
+ }
|
|
|
+ update_registers(pmx_settings[i], true);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
}
|
|
|
+
|
|
|
+#if (defined(CONFIG_DEBUG_FS) && defined(CONFIG_U300_DEBUG))
|
|
|
+static int pmx_show(struct seq_file *s, void *data)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ seq_printf(s, "-------------------------------------------------\n");
|
|
|
+ seq_printf(s, "SETTING BOUND TO DEVICE STATE\n");
|
|
|
+ seq_printf(s, "-------------------------------------------------\n");
|
|
|
+ mutex_lock(&pmx_mutex);
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pmx_settings); i++) {
|
|
|
+ /* Format pmx and device name nicely */
|
|
|
+ char cdp[33];
|
|
|
+ int chars;
|
|
|
+
|
|
|
+ chars = snprintf(&cdp[0], 17, "%s", pmx_settings[i]->name);
|
|
|
+ while (chars < 16) {
|
|
|
+ cdp[chars] = ' ';
|
|
|
+ chars++;
|
|
|
+ }
|
|
|
+ chars = snprintf(&cdp[16], 17, "%s", pmx_settings[i]->dev ?
|
|
|
+ dev_name(pmx_settings[i]->dev) : "N/A");
|
|
|
+ while (chars < 16) {
|
|
|
+ cdp[chars+16] = ' ';
|
|
|
+ chars++;
|
|
|
+ }
|
|
|
+ cdp[32] = '\0';
|
|
|
+
|
|
|
+ seq_printf(s,
|
|
|
+ "%s\t%s\n",
|
|
|
+ &cdp[0],
|
|
|
+ pmx_settings[i]->activated ?
|
|
|
+ "ACTIVATED" : "DEACTIVATED"
|
|
|
+ );
|
|
|
+
|
|
|
+ }
|
|
|
+ mutex_unlock(&pmx_mutex);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int pmx_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return single_open(file, pmx_show, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct file_operations pmx_operations = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .open = pmx_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = single_release,
|
|
|
+};
|
|
|
+
|
|
|
+static int __init init_pmx_read_debugfs(void)
|
|
|
+{
|
|
|
+ /* Expose a simple debugfs interface to view pmx settings */
|
|
|
+ (void) debugfs_create_file("padmux", S_IFREG | S_IRUGO,
|
|
|
+ NULL, NULL,
|
|
|
+ &pmx_operations);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * This needs to come in after the core_initcall(),
|
|
|
+ * because debugfs is not available until
|
|
|
+ * the subsystems come up.
|
|
|
+ */
|
|
|
+module_init(init_pmx_read_debugfs);
|
|
|
+#endif
|
|
|
+
|
|
|
+static int __init pmx_init(void)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = pmx_set_default();
|
|
|
+
|
|
|
+ if (IS_ERR_VALUE(ret))
|
|
|
+ pr_crit("padmux: default settings could not be set\n");
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Should be initialized before consumers */
|
|
|
+core_initcall(pmx_init);
|