linux/arch/arm/mach-u300/padmux.c

368 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);