summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/powerdetect.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-07-20 16:15:25 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 00:56:44 -0700
commit017db92f53d92a812a3aa00bb6b57bf13c1512ad (patch)
treea2c9faa3ee6027484da746d0eb246eaa835771c9 /arch/arm/mach-tegra/powerdetect.c
parent9e83f078a8871024d763359bc609a7ab3f684307 (diff)
ARM: tegra: power: Control IO pad configuration dynamically
Tegra IO pads are automatically re-configured when IO power level is changed. Current code keeps auto-detection cells in default, active state all the time. This change will allow turning off cells when IO power is stable, and activate them only during power transitions. In addition IO pads will be set into "no-io-power" state after the respective regulator is disabled, and re-configured back for regular operations before regulator is re-enabled. Dynamic IO pad control introduced in this commit is still disabled by default on all tegra platforms. Bug 853132 Original-Change-Id: Ifc7bbe2ac34929c14f8f8e9feaa4290b78fe6cf6 Reviewed-on: http://git-master/r/42263 Reviewed-by: Varun Colbert <vcolbert@nvidia.com> Tested-by: Varun Colbert <vcolbert@nvidia.com> Rebase-Id: R8b7c7863c1580816a2f3b28bdb3c228a97a18736
Diffstat (limited to 'arch/arm/mach-tegra/powerdetect.c')
-rw-r--r--arch/arm/mach-tegra/powerdetect.c321
1 files changed, 321 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/powerdetect.c b/arch/arm/mach-tegra/powerdetect.c
new file mode 100644
index 000000000000..ee71cd1cbdf7
--- /dev/null
+++ b/arch/arm/mach-tegra/powerdetect.c
@@ -0,0 +1,321 @@
+/*
+ * arch/arm/mach-tegra/powerdetect.c
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/notifier.h>
+#include <linux/regulator/consumer.h>
+#include <linux/moduleparam.h>
+
+#include <mach/iomap.h>
+
+#define PMC_PWR_IO_DISABLE 0x44
+#define PMC_PWR_DET_ENABLE 0x48
+#define PMC_PWR_DET_LATCH 0x4C
+#define PMC_PWR_DET_VAL 0xE4
+
+struct pwr_detect_cell {
+ const char *reg_id;
+ u32 pwrdet_mask;
+ u32 pwrio_mask;
+
+ struct notifier_block regulator_nb;
+};
+
+static bool pwrdet_rails_found;
+static bool pwrdet_always_on;
+static bool pwrio_always_on;
+static u32 pwrdet_val;
+static u32 pwrio_val;
+static u32 pwrio_disabled_mask;
+
+static DEFINE_SPINLOCK(pwr_lock);
+
+static void __iomem *pmc_base = IO_ADDRESS(TEGRA_PMC_BASE);
+
+static inline void pmc_writel(u32 val, unsigned long addr)
+{
+ writel(val, pmc_base + addr);
+}
+static inline u32 pmc_readl(unsigned long addr)
+{
+ return readl(pmc_base + addr);
+}
+
+
+#define POWER_CELL(_reg_id, _pwrdet_mask, _pwrio_mask) \
+ { \
+ .reg_id = _reg_id, \
+ .pwrdet_mask = _pwrdet_mask, \
+ .pwrio_mask = _pwrio_mask, \
+ }
+
+/* Some IO pads does not have power detect cells, but still can/should be
+ * turned off when no power - set pwrdet_mask=0 for such pads */
+static struct pwr_detect_cell pwr_detect_cells[] = {
+ POWER_CELL("pwrdet_nand", (0x1 << 1), (0x1 << 1)),
+ POWER_CELL("pwrdet_uart", (0x1 << 2), (0x1 << 2)),
+ POWER_CELL("pwrdet_bb", (0x1 << 3), (0x1 << 3)),
+ POWER_CELL("pwrdet_vi", 0, (0x1 << 4)),
+ POWER_CELL("pwrdet_audio", (0x1 << 5), (0x1 << 5)),
+ POWER_CELL("pwrdet_lcd", (0x1 << 6), (0x1 << 6)),
+ POWER_CELL("pwrdet_mipi", 0, (0x1 << 9)),
+ POWER_CELL("pwrdet_cam", (0x1 << 10), (0x1 << 10)),
+ POWER_CELL("pwrdet_pex_ctl", (0x1 << 11), (0x1 << 11)),
+ POWER_CELL("pwrdet_sdmmc1", (0x1 << 12), (0x1 << 12)),
+ POWER_CELL("pwrdet_sdmmc3", (0x1 << 13), (0x1 << 13)),
+ POWER_CELL("pwrdet_sdmmc4", 0, (0x1 << 14)),
+};
+
+static void pwr_detect_reset(u32 pwrdet_mask)
+{
+ pmc_writel(pwrdet_mask, PMC_PWR_DET_ENABLE);
+ barrier();
+ pmc_writel(pwrdet_mask, PMC_PWR_DET_VAL);
+
+ pmc_readl(PMC_PWR_DET_VAL);
+ pmc_writel(0, PMC_PWR_DET_ENABLE);
+}
+
+static void pwr_detect_start(u32 pwrdet_mask)
+{
+ pmc_writel(pwrdet_mask, PMC_PWR_DET_ENABLE);
+ udelay(4);
+
+ pmc_writel(1, PMC_PWR_DET_LATCH);
+ pmc_readl(PMC_PWR_DET_LATCH);
+}
+
+static void pwr_detect_latch(void)
+{
+ pmc_writel(0, PMC_PWR_DET_LATCH);
+
+ pmc_readl(PMC_PWR_DET_VAL);
+ pmc_writel(0, PMC_PWR_DET_ENABLE);
+}
+
+static void pwr_io_enable(u32 pwrio_mask)
+{
+ u32 val = pmc_readl(PMC_PWR_IO_DISABLE);
+ val &= ~pwrio_mask;
+ pmc_writel(val, PMC_PWR_IO_DISABLE);
+}
+
+static void pwr_io_disable(u32 pwrio_mask)
+{
+ u32 val = pmc_readl(PMC_PWR_IO_DISABLE);
+ val |= pwrio_mask;
+ pmc_writel(val, PMC_PWR_IO_DISABLE);
+}
+
+static int pwrdet_always_on_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pwr_lock, flags);
+
+ ret = param_set_bool(arg, kp);
+ if (ret) {
+ spin_unlock_irqrestore(&pwr_lock, flags);
+ return ret;
+ }
+
+ if (pwrdet_always_on)
+ pwr_detect_start(0xFFFFFFFF);
+ else
+ pwr_detect_latch();
+
+ spin_unlock_irqrestore(&pwr_lock, flags);
+ return 0;
+}
+
+static int pwrio_always_on_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pwr_lock, flags);
+
+ ret = param_set_bool(arg, kp);
+ if (ret) {
+ spin_unlock_irqrestore(&pwr_lock, flags);
+ return ret;
+ }
+
+ if (pwrio_always_on)
+ pwr_io_enable(0xFFFFFFFF);
+ else
+ pwr_io_disable(pwrio_disabled_mask);
+
+ spin_unlock_irqrestore(&pwr_lock, flags);
+ return 0;
+}
+
+static int pwrdet_always_on_get(char *buffer, const struct kernel_param *kp)
+{
+ return param_get_bool(buffer, kp);
+}
+
+static struct kernel_param_ops pwrdet_always_on_ops = {
+ .set = pwrdet_always_on_set,
+ .get = pwrdet_always_on_get,
+};
+static struct kernel_param_ops pwrio_always_on_ops = {
+ .set = pwrio_always_on_set,
+ .get = pwrdet_always_on_get,
+};
+module_param_cb(pwrdet_always_on, &pwrdet_always_on_ops,
+ &pwrdet_always_on, 0644);
+module_param_cb(pwrio_always_on, &pwrio_always_on_ops,
+ &pwrio_always_on, 0644);
+
+static int pwrdet_val_get(char *buffer, const struct kernel_param *kp)
+{
+ pwrdet_val = pmc_readl(PMC_PWR_DET_VAL);
+ return param_get_ulong(buffer, kp);
+}
+static struct kernel_param_ops pwrdet_val_ops = {
+ .get = pwrdet_val_get,
+};
+module_param_cb(pwrdet_val, &pwrdet_val_ops, &pwrdet_val, 0444);
+
+static int pwrio_val_get(char *buffer, const struct kernel_param *kp)
+{
+ pwrio_val = pmc_readl(PMC_PWR_IO_DISABLE);
+ return param_get_ulong(buffer, kp);
+}
+static struct kernel_param_ops pwrio_val_ops = {
+ .get = pwrio_val_get,
+};
+module_param_cb(pwrio_val, &pwrio_val_ops, &pwrio_val, 0444);
+
+
+static int pwrdet_notify_cb(
+ struct notifier_block *nb, unsigned long event, void *v)
+{
+ unsigned long flags;
+ struct pwr_detect_cell *cell;
+
+ if (!pwrdet_rails_found)
+ return NOTIFY_OK;
+
+ cell = container_of(nb, struct pwr_detect_cell, regulator_nb);
+
+ spin_lock_irqsave(&pwr_lock, flags);
+
+ switch (event) {
+ case REGULATOR_EVENT_PRE_ENABLE:
+ pwrio_disabled_mask &= ~cell->pwrio_mask;
+ if (!pwrio_always_on)
+ pwr_io_enable(cell->pwrio_mask);
+ /* fall thru */
+ case REGULATOR_EVENT_OUT_PRECHANGE:
+ if (!pwrdet_always_on && cell->pwrdet_mask)
+ pwr_detect_reset(cell->pwrdet_mask);
+ break;
+
+ case REGULATOR_EVENT_POST_ENABLE:
+ case REGULATOR_EVENT_OUT_POSTCHANGE:
+ if (!pwrdet_always_on && cell->pwrdet_mask) {
+ pwr_detect_start(cell->pwrdet_mask);
+ pwr_detect_latch();
+ }
+ break;
+
+ case REGULATOR_EVENT_DISABLE:
+ case REGULATOR_EVENT_FORCE_DISABLE:
+ pwrio_disabled_mask |= cell->pwrio_mask;
+ if (!pwrio_always_on)
+ pwr_io_disable(cell->pwrio_mask);
+ break;
+ }
+
+ pr_debug("tegra: %s: event %lu, pwrdet 0x%x, pwrio 0x%x\n",
+ cell->reg_id, event,
+ pmc_readl(PMC_PWR_DET_VAL), pmc_readl(PMC_PWR_IO_DISABLE));
+ spin_unlock_irqrestore(&pwr_lock, flags);
+
+ return NOTIFY_OK;
+}
+
+static int __init pwr_detect_cell_init_one(
+ struct pwr_detect_cell *cell, u32 *disabled_mask)
+{
+ int ret;
+ struct regulator *regulator = regulator_get(NULL, cell->reg_id);
+
+ if (IS_ERR(regulator))
+ return PTR_ERR(regulator);
+
+ cell->regulator_nb.notifier_call = pwrdet_notify_cb;
+ ret = regulator_register_notifier(regulator, &cell->regulator_nb);
+ if (ret) {
+ regulator_put(regulator);
+ return ret;
+ }
+
+ if (!regulator_is_enabled(regulator))
+ *disabled_mask |= cell->pwrio_mask;
+
+ regulator_put(regulator);
+ return 0;
+}
+
+int __init tegra_pwr_detect_cell_init(void)
+{
+ int i, ret;
+ unsigned long flags;
+ bool rails_found = true;
+
+ for (i = 0; i < ARRAY_SIZE(pwr_detect_cells); i++) {
+ struct pwr_detect_cell *cell = &pwr_detect_cells[i];
+ ret = pwr_detect_cell_init_one(cell, &pwrio_disabled_mask);
+ if (ret) {
+ pr_err("tegra: failed to map regulator to power detect"
+ " cell %s(%d)\n", cell->reg_id, ret);
+ rails_found = false;
+ }
+ }
+
+ if (!rails_found) {
+ pr_err("tegra: io power detection is left always on\n");
+ return 0;
+ }
+ pwrdet_rails_found = true;
+
+ /* Latch initial i/o power levels, disable all detection cells
+ and not powered interfaces */
+ spin_lock_irqsave(&pwr_lock, flags);
+ if (!pwrdet_always_on)
+ pwr_detect_latch();
+ if (!pwrio_always_on)
+ pwr_io_disable(pwrio_disabled_mask);
+ spin_unlock_irqrestore(&pwr_lock, flags);
+
+ pr_info("tegra: started io power detection dynamic control\n");
+ pr_info("tegra: NO_IO_POWER setting 0x%x\n", pwrio_disabled_mask);
+
+ return 0;
+}
+
+fs_initcall(tegra_pwr_detect_cell_init);