This abstracts the hackish padmux API on the U300 platform into something more manageable. It provides a way for drivers to activate/deactivate a certain padmux setting. It will also switch the users of the old API over to using the new style, pushing muxing into the apropriate setup files. Signed-off-by: Linus Walleij <linus.walleij@stericsson.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
		
			
				
	
	
		
			367 lines
		
	
	
	
		
			7.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
	
		
			7.8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *
 | 
						|
 * arch/arm/mach-u300/padmux.c
 | 
						|
 *
 | 
						|
 *
 | 
						|
 * Copyright (C) 2009 ST-Ericsson AB
 | 
						|
 * License terms: GNU General Public License (GPL) version 2
 | 
						|
 * U300 PADMUX functions
 | 
						|
 * Author: Martin Persson <martin.persson@stericsson.com>
 | 
						|
 */
 | 
						|
 | 
						|
#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"
 | 
						|
 | 
						|
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);
 |