blob: 13c22ddd7ea3817dce9755588d56abeb0fc8e820 [file] [log] [blame]
/*
* arch/arm/plat-ambarella/generic/pm.c
* Power Management Routines
* Author: Anthony Ginger <hfjiang@ambarella.com>
*
* Copyright (C) 2004-2009, Ambarella, Inc.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/cpu.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <asm/cacheflush.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/suspend.h>
#include <mach/hardware.h>
#include <mach/init.h>
#include <plat/drctl.h>
#include <plat/rtc.h>
#include <plat/fio.h>
#define SREF_MAGIC_PATTERN 0x43525230
#define SREF_CPU_JUMP_ADDR 0x000f1000
static int dram_reset_ctrl = -1;
static int gpio_notify_mcu = -1;
static int hibernate_gpio_notify_mcu = -1;
extern int ambarella_suspend_trigger_signal[];
u32 gpio_regbase[] = {GPIO0_BASE, GPIO1_BASE, GPIO2_BASE, GPIO3_BASE,
GPIO4_BASE, GPIO5_BASE, GPIO6_BASE};
static unsigned long ambarella_gpio_setup(int);
/* ==========================================================================*/
void ambarella_pm_gpiomux(int gpio)
{
u32 bank, offset;
bank = PINID_TO_BANK(gpio);
offset = PINID_TO_OFFSET(gpio);
#if (IOMUX_SUPPORT > 0)
/* configure the pin as GPIO mode */
amba_clrbitsl(IOMUX_REG(IOMUX_REG_OFFSET(bank, 0)), 0x1 << offset);
amba_clrbitsl(IOMUX_REG(IOMUX_REG_OFFSET(bank, 1)), 0x1 << offset);
amba_clrbitsl(IOMUX_REG(IOMUX_REG_OFFSET(bank, 2)), 0x1 << offset);
amba_writel(IOMUX_REG(IOMUX_CTRL_SET_OFFSET), 0x1);
amba_writel(IOMUX_REG(IOMUX_CTRL_SET_OFFSET), 0x0);
#endif
}
void ambarella_pm_gpio_output(int gpio, int value)
{
u32 bank, offset;
bank = PINID_TO_BANK(gpio);
offset = PINID_TO_OFFSET(gpio);
amba_writel(gpio_regbase[bank] + GPIO_ENABLE_OFFSET, 0xffffffff);
amba_clrbitsl(gpio_regbase[bank] + GPIO_AFSEL_OFFSET, 0x1 << offset);
amba_setbitsl(gpio_regbase[bank] + GPIO_MASK_OFFSET, 0x1 << offset);
amba_setbitsl(gpio_regbase[bank] + GPIO_DIR_OFFSET, 0x1 << offset);
if (!!value)
amba_setbitsl(gpio_regbase[bank] + GPIO_DATA_OFFSET, 0x1 << offset);
else
amba_clrbitsl(gpio_regbase[bank] + GPIO_DATA_OFFSET, 0x1 << offset);
}
void ambarella_pm_gpio_input(int gpio, int *value)
{
u32 bank, offset;
bank = PINID_TO_BANK(gpio);
offset = PINID_TO_OFFSET(gpio);
amba_writel(gpio_regbase[bank] + GPIO_ENABLE_OFFSET, 0xffffffff);
amba_clrbitsl(gpio_regbase[bank] + GPIO_AFSEL_OFFSET, 0x1 << offset);
amba_setbitsl(gpio_regbase[bank] + GPIO_MASK_OFFSET, 0x1 << offset);
amba_clrbitsl(gpio_regbase[bank] + GPIO_DIR_OFFSET, 0x1 << offset);
*value = !!(amba_readl(gpio_regbase[bank] + GPIO_DATA_OFFSET) & 0x1 << offset);
}
/* ==========================================================================*/
void ambarella_power_off(void)
{
if (!ambarella_gpio_setup(hibernate_gpio_notify_mcu))
return;
ambarella_pm_gpio_output(hibernate_gpio_notify_mcu, 1);
mdelay(100);
ambarella_pm_gpio_output(hibernate_gpio_notify_mcu, 0);
amba_rct_setbitsl(ANA_PWR_REG, ANA_PWR_POWER_DOWN);
}
void ambarella_power_off_prepare(void)
{
}
/* ==========================================================================*/
static void ambarella_disconnect_dram_reset(void)
{
if (dram_reset_ctrl == -1)
return;
ambarella_pm_gpiomux(dram_reset_ctrl);
ambarella_pm_gpio_output(dram_reset_ctrl, 0);
}
static unsigned long ambarella_gpio_setup(int gpio)
{
unsigned long gpio_info;
u32 bank, offset;
if (gpio == -1)
return 0;
bank = PINID_TO_BANK(gpio);
offset = PINID_TO_OFFSET(gpio);
gpio_info = gpio_regbase[bank] | offset;
/* Mux GPIO output mode */
ambarella_pm_gpio_output(gpio, !ambarella_suspend_trigger_signal[0]);
ambarella_pm_gpiomux(gpio);
return gpio_info;
}
static void ambarella_set_cpu_jump(int cpu, void *jump_fn)
{
u32 addr_phys;
u32 *addr_virt;
/* must keep consistent with self_refresh.c in bst. */
addr_phys = get_ambarella_ppm_phys() + SREF_CPU_JUMP_ADDR;
addr_virt = (u32 *)ambarella_phys_to_virt(addr_phys);
*addr_virt++ = SREF_MAGIC_PATTERN;
*addr_virt = virt_to_phys(jump_fn);
__cpuc_flush_dcache_area(addr_virt, sizeof(u32) * 2);
outer_clean_range(addr_phys, addr_phys + sizeof(u32) * 2);
}
static int ambarella_cpu_do_idle(unsigned long unused)
{
cpu_do_idle();
return 0;
}
static int ambarella_pm_enter_standby(void)
{
cpu_suspend(0, ambarella_cpu_do_idle);
return 0;
}
static void ambarella_pm_rtc_flush(void)
{
unsigned int alarm;
unsigned int time;
alarm = amba_readl(RTC_REG(RTC_ALAT_OFFSET));
amba_writel(RTC_REG(RTC_PWC_ALAT_OFFSET), alarm);
time = amba_readl(RTC_REG(RTC_CURT_OFFSET));
amba_writel(RTC_REG(RTC_PWC_CURT_OFFSET), time);
}
static unsigned long ambarella_pm_pwc_trigger(void)
{
/* ensure the power for DRAM keeps on when power off PWC */
amba_writel(RTC_REG(RTC_PWC_ENP3_OFFSET), 0x1);
amba_writel(RTC_REG(RTC_POS0_OFFSET), 0x10);
amba_writel(RTC_REG(RTC_POS1_OFFSET), 0x10);
amba_writel(RTC_REG(RTC_POS2_OFFSET), 0x10);
amba_writel(RTC_REG(RTC_POS3_OFFSET), 0x10);
amba_setbitsl(RTC_REG(RTC_PWC_SET_STATUS_OFFSET), 0x04);
ambarella_pm_rtc_flush();
amba_writel(RTC_REG(RTC_RESET_OFFSET), 0x1);
mdelay(3);
amba_writel(RTC_REG(RTC_RESET_OFFSET), 0x0);
return 0;
}
static void ambarella_pm_pwc_resume(void)
{
/* ensure to power off all powers when power off PWC */
amba_writel(RTC_REG(RTC_PWC_ENP3_OFFSET), 0x0);
amba_clrbitsl(RTC_REG(RTC_PWC_SET_STATUS_OFFSET), 0x04);
ambarella_pm_rtc_flush();
amba_writel(RTC_REG(RTC_RESET_OFFSET), 0x1);
while(amba_readl(RTC_REG(RTC_PWC_REG_STA_OFFSET)) & 0x04);
amba_writel(RTC_REG(RTC_RESET_OFFSET), 0x0);
}
static unsigned long ambarella_pm_io_trigger(void)
{
/* FIXME: flush internel rtc on chips, even though it is disabled. Because
* it has no unexpected effect on system */
ambarella_pm_rtc_flush();
return ambarella_gpio_setup(gpio_notify_mcu);
}
static void ambarella_pm_io_resume(void)
{
ambarella_pm_rtc_flush();
}
static int ambarella_pm_enter_mem(void)
{
unsigned long arg = 0;
#ifdef CONFIG_AMBARELLA_SREF_FIFO_EXEC
void *ambarella_suspend_exec = NULL;
#endif
ambarella_disconnect_dram_reset();
if (gpio_notify_mcu == -1)
arg = ambarella_pm_pwc_trigger();
else
arg = ambarella_pm_io_trigger();
ambarella_set_cpu_jump(0, ambarella_cpu_resume);
#ifdef CONFIG_AMBARELLA_SREF_FIFO_EXEC
ambarella_suspend_exec = ambarella_fio_push(ambarella_optimize_suspend,
ambarella_optimize_suspend_sz);
#endif
outer_flush_all();
outer_disable();
#ifdef CONFIG_AMBARELLA_SREF_FIFO_EXEC
cpu_suspend(arg, ambarella_suspend_exec);
#else
cpu_suspend(0, ambarella_finish_suspend);
#endif
outer_resume();
if (gpio_notify_mcu == -1)
ambarella_pm_pwc_resume();
else
ambarella_pm_io_resume();
return 0;
}
static int ambarella_pm_suspend_enter(suspend_state_t state)
{
int rval = 0;
switch (state) {
case PM_SUSPEND_STANDBY:
rval = ambarella_pm_enter_standby();
break;
case PM_SUSPEND_MEM:
rval = ambarella_pm_enter_mem();
break;
case PM_SUSPEND_ON:
default:
break;
}
return rval;
}
static int ambarella_pm_suspend_valid(suspend_state_t state)
{
int valid;
switch (state) {
case PM_SUSPEND_ON:
case PM_SUSPEND_STANDBY:
case PM_SUSPEND_MEM:
valid = 1;
break;
default:
valid = 0;
break;
}
pr_debug("%s: state[%d]=%d\n", __func__, state, valid);
return valid;
}
static struct platform_suspend_ops ambarella_pm_suspend_ops = {
.valid = ambarella_pm_suspend_valid,
.enter = ambarella_pm_suspend_enter,
};
/* ==========================================================================*/
int __init ambarella_init_pm(void)
{
pm_power_off = ambarella_power_off;
pm_power_off_prepare = ambarella_power_off_prepare;
suspend_set_ops(&ambarella_pm_suspend_ops);
of_property_read_u32(of_chosen, "ambarella,dram-reset-ctrl", &dram_reset_ctrl);
of_property_read_u32(of_chosen, "ambarella,gpio-notify-mcu", &gpio_notify_mcu);
of_property_read_u32(of_chosen, "ambarella,hibernate-gpio-notify-mcu", &hibernate_gpio_notify_mcu);
ambarella_suspend_trigger_signal[0] = !!of_find_property(of_chosen,
"ambarella,gpio-trigger-high", NULL);
WARN(dram_reset_ctrl >= GPIO_MAX_LINES, "Invalid DRAM RESET GPIO: %d\n", dram_reset_ctrl);
WARN(gpio_notify_mcu >= GPIO_MAX_LINES, "Invalid MCU NOTIFY GPIO: %d\n", gpio_notify_mcu);
pr_info("Ambarella power management: Wed Aug 10 2016 %s\n",
(gpio_notify_mcu == -1) ? "": ambarella_suspend_trigger_signal[0] ? "[positive edge]":"[negative edge]");
return 0;
}