217 lines
5.3 KiB
C
217 lines
5.3 KiB
C
/* Copyright (c) 2010,2013, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/io.h>
|
|
#include <mach/gpiomux.h>
|
|
#include <mach/msm_iomap.h>
|
|
|
|
struct msm_gpiomux_rec {
|
|
struct gpiomux_setting *sets[GPIOMUX_NSETTINGS];
|
|
int ref;
|
|
};
|
|
static DEFINE_SPINLOCK(gpiomux_lock);
|
|
static struct msm_gpiomux_rec *msm_gpiomux_recs;
|
|
static struct gpiomux_setting *msm_gpiomux_sets;
|
|
static unsigned msm_gpiomux_ngpio;
|
|
|
|
static int msm_gpiomux_store(unsigned gpio, enum msm_gpiomux_setting which,
|
|
struct gpiomux_setting *setting, struct gpiomux_setting *old_setting)
|
|
{
|
|
struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio;
|
|
unsigned set_slot = gpio * GPIOMUX_NSETTINGS + which;
|
|
unsigned long irq_flags;
|
|
int status = 0;
|
|
|
|
if (!msm_gpiomux_recs)
|
|
return -EFAULT;
|
|
|
|
if (gpio >= msm_gpiomux_ngpio)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&gpiomux_lock, irq_flags);
|
|
|
|
if (old_setting) {
|
|
if (rec->sets[which] == NULL)
|
|
status = 1;
|
|
else
|
|
*old_setting = *(rec->sets[which]);
|
|
}
|
|
|
|
if (setting) {
|
|
msm_gpiomux_sets[set_slot] = *setting;
|
|
rec->sets[which] = &msm_gpiomux_sets[set_slot];
|
|
} else {
|
|
rec->sets[which] = NULL;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&gpiomux_lock, irq_flags);
|
|
return status;
|
|
}
|
|
|
|
int msm_gpiomux_write(unsigned gpio, enum msm_gpiomux_setting which,
|
|
struct gpiomux_setting *setting, struct gpiomux_setting *old_setting)
|
|
{
|
|
int ret;
|
|
unsigned long irq_flags;
|
|
struct gpiomux_setting *new_set;
|
|
struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio;
|
|
|
|
ret = msm_gpiomux_store(gpio, which, setting, old_setting);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
spin_lock_irqsave(&gpiomux_lock, irq_flags);
|
|
|
|
new_set = rec->ref ? rec->sets[GPIOMUX_ACTIVE] :
|
|
rec->sets[GPIOMUX_SUSPENDED];
|
|
if (new_set)
|
|
__msm_gpiomux_write(gpio, *new_set);
|
|
|
|
spin_unlock_irqrestore(&gpiomux_lock, irq_flags);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_write);
|
|
|
|
int msm_gpiomux_get(unsigned gpio)
|
|
{
|
|
struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio;
|
|
unsigned long irq_flags;
|
|
|
|
if (!msm_gpiomux_recs)
|
|
return -EFAULT;
|
|
|
|
if (gpio >= msm_gpiomux_ngpio)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&gpiomux_lock, irq_flags);
|
|
if (rec->ref++ == 0 && rec->sets[GPIOMUX_ACTIVE])
|
|
__msm_gpiomux_write(gpio, *rec->sets[GPIOMUX_ACTIVE]);
|
|
spin_unlock_irqrestore(&gpiomux_lock, irq_flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_get);
|
|
|
|
int msm_gpiomux_put(unsigned gpio)
|
|
{
|
|
struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio;
|
|
unsigned long irq_flags;
|
|
|
|
if (!msm_gpiomux_recs)
|
|
return -EFAULT;
|
|
|
|
if (gpio >= msm_gpiomux_ngpio)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&gpiomux_lock, irq_flags);
|
|
BUG_ON(rec->ref == 0);
|
|
if (--rec->ref == 0 && rec->sets[GPIOMUX_SUSPENDED])
|
|
__msm_gpiomux_write(gpio, *rec->sets[GPIOMUX_SUSPENDED]);
|
|
spin_unlock_irqrestore(&gpiomux_lock, irq_flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_put);
|
|
|
|
void msm_tlmm_misc_reg_write(enum msm_tlmm_misc_reg misc_reg, int val)
|
|
{
|
|
writel_relaxed(val, MSM_TLMM_BASE + misc_reg);
|
|
/* ensure the write completes before returning */
|
|
mb();
|
|
}
|
|
|
|
int msm_gpiomux_init(size_t ngpio)
|
|
{
|
|
if (!ngpio)
|
|
return -EINVAL;
|
|
|
|
if (msm_gpiomux_recs)
|
|
return -EPERM;
|
|
|
|
msm_gpiomux_recs = kzalloc(sizeof(struct msm_gpiomux_rec) * ngpio,
|
|
GFP_KERNEL);
|
|
if (!msm_gpiomux_recs)
|
|
return -ENOMEM;
|
|
|
|
/* There is no need to zero this memory, as clients will be blindly
|
|
* installing settings on top of it.
|
|
*/
|
|
msm_gpiomux_sets = kmalloc(sizeof(struct gpiomux_setting) * ngpio *
|
|
GPIOMUX_NSETTINGS, GFP_KERNEL);
|
|
if (!msm_gpiomux_sets) {
|
|
kfree(msm_gpiomux_recs);
|
|
msm_gpiomux_recs = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
msm_gpiomux_ngpio = ngpio;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_init);
|
|
|
|
void msm_gpiomux_install_nowrite(struct msm_gpiomux_config *configs,
|
|
unsigned nconfigs)
|
|
{
|
|
unsigned c, s;
|
|
int rc;
|
|
|
|
for (c = 0; c < nconfigs; ++c) {
|
|
for (s = 0; s < GPIOMUX_NSETTINGS; ++s) {
|
|
rc = msm_gpiomux_store(configs[c].gpio, s,
|
|
configs[c].settings[s], NULL);
|
|
if (rc)
|
|
pr_err("%s: write failure: %d\n", __func__, rc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void msm_gpiomux_install(struct msm_gpiomux_config *configs, unsigned nconfigs)
|
|
{
|
|
unsigned c, s;
|
|
int rc;
|
|
|
|
for (c = 0; c < nconfigs; ++c) {
|
|
for (s = 0; s < GPIOMUX_NSETTINGS; ++s) {
|
|
rc = msm_gpiomux_write(configs[c].gpio, s,
|
|
configs[c].settings[s], NULL);
|
|
if (rc)
|
|
pr_err("%s: write failure: %d\n", __func__, rc);
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_install);
|
|
|
|
int msm_gpiomux_init_dt(void)
|
|
{
|
|
int rc;
|
|
unsigned int ngpio;
|
|
struct device_node *of_gpio_node;
|
|
|
|
of_gpio_node = of_find_compatible_node(NULL, NULL, "qcom,msm-gpio");
|
|
if (!of_gpio_node) {
|
|
pr_err("%s: Failed to find qcom,msm-gpio node\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = of_property_read_u32(of_gpio_node, "ngpio", &ngpio);
|
|
if (rc) {
|
|
pr_err("%s: Failed to find ngpio property in msm-gpio device node %d\n"
|
|
, __func__, rc);
|
|
return rc;
|
|
}
|
|
|
|
return msm_gpiomux_init(ngpio);
|
|
}
|
|
EXPORT_SYMBOL(msm_gpiomux_init_dt);
|