/*
* 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 <mach/iomap.h>
#include "board.h"
#include "fuse.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;
u32 package_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, (u32)pmc_base + addr);
}
static inline u32 pmc_readl(unsigned long addr)
{
return readl((u32)pmc_base + addr);
}
#define POWER_CELL(_reg_id, _pwrdet_mask, _pwrio_mask, _package_mask) \
{ \
.reg_id = _reg_id, \
.pwrdet_mask = _pwrdet_mask, \
.pwrio_mask = _pwrio_mask, \
.package_mask = _package_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), 0xFFFFFFFF),
POWER_CELL("pwrdet_uart", (0x1 << 2), (0x1 << 2), 0xFFFFFFFF),
POWER_CELL("pwrdet_bb", (0x1 << 3), (0x1 << 3), 0xFFFFFFFF),
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
POWER_CELL("pwrdet_vi", 0, (0x1 << 4), 0xFFFFFFFF),
#else
/* Tegra3 VI is connected on MID package only (id = 1, mask = 0x2) */
POWER_CELL("pwrdet_vi", 0, (0x1 << 4), 0x00000002),
#endif
POWER_CELL("pwrdet_audio", (0x1 << 5), (0x1 << 5), 0xFFFFFFFF),
POWER_CELL("pwrdet_lcd", (0x1 << 6), (0x1 << 6), 0xFFFFFFFF),
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
POWER_CELL("pwrdet_sd", 0, (0x1 << 8), 0xFFFFFFFF),
#endif
POWER_CELL("pwrdet_mipi", 0, (0x1 << 9), 0xFFFFFFFF),
#ifndef CONFIG_ARCH_TEGRA_2x_SOC
POWER_CELL("pwrdet_cam", (0x1 << 10), (0x1 << 10), 0xFFFFFFFF),
POWER_CELL("pwrdet_pex_ctl", (0x1 << 11), (0x1 << 11), 0xFFFFFFFF),
POWER_CELL("pwrdet_sdmmc1", (0x1 << 12), (0x1 << 12), 0xFFFFFFFF),
POWER_CELL("pwrdet_sdmmc3", (0x1 << 13), (0x1 << 13), 0xFFFFFFFF),
POWER_CELL("pwrdet_sdmmc4", 0, (0x1 << 14), 0xFFFFFFFF),
#endif
};
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);
if (event & REGULATOR_EVENT_PRE_ENABLE) {
pwrio_disabled_mask &= ~cell->pwrio_mask;
if (!pwrio_always_on)
pwr_io_enable(cell->pwrio_mask);
}
if (event & (REGULATOR_EVENT_PRE_ENABLE |
REGULATOR_EVENT_OUT_PRECHANGE)) {
if (!pwrdet_always_on && cell->pwrdet_mask)
pwr_detect_reset(cell->pwrdet_mask);
}
if (event & (REGULATOR_EVENT_POST_ENABLE |
REGULATOR_EVENT_OUT_POSTCHANGE)) {
if (!pwrdet_always_on && cell->pwrdet_mask) {
pwr_detect_start(cell->pwrdet_mask);
pwr_detect_latch();
}
}
if (event & (REGULATOR_EVENT_DISABLE |
REGULATOR_EVENT_FORCE_DISABLE)) {
pwrio_disabled_mask |= cell->pwrio_mask;
if (!pwrio_always_on)
pwr_io_disable(cell->pwrio_mask);
}
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;
u32 package_mask;
unsigned long flags;
bool rails_found = true;
i = tegra_package_id();
if ((i != -1) && (i & (~0x1F))) {
pr_err("tegra: not supported package id %d - io power detection"
" is left always on\n", i);
return 0;
}
package_mask = (i == -1) ? i : (0x1 << i);
for (i = 0; i < ARRAY_SIZE(pwr_detect_cells); i++) {
struct pwr_detect_cell *cell = &pwr_detect_cells[i];
if (!(cell->package_mask & package_mask)) {
pwrio_disabled_mask |= cell->pwrio_mask;
continue;
}
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: failed regulators mapping - 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);