/* * * 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);