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