blob: a93963b566d157b2d68470e53cffcd0fe3ae373f [file] [log] [blame]
/*
* Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/suspend.h>
#include <linux/regulator/machine.h>
#include <linux/proc_fs.h>
#include <linux/iram_alloc.h>
#include <linux/fsl_devices.h>
#include <asm/mach-types.h>
#include <asm/cacheflush.h>
#include <asm/tlb.h>
#include <asm/delay.h>
#include <asm/mach/map.h>
#include <mach/hardware.h>
#include <mach/imx-pm.h>
#include <asm/hardware/cache-l2x0.h>
#include <asm/hardware/gic.h>
#ifdef CONFIG_ARCH_MX6Q
#include <mach/iomux-mx6q.h>
#endif
#include "crm_regs.h"
#include "src-reg.h"
#define SCU_CTRL_OFFSET 0x00
#define GPC_IMR1_OFFSET 0x08
#define GPC_IMR2_OFFSET 0x0c
#define GPC_IMR3_OFFSET 0x10
#define GPC_IMR4_OFFSET 0x14
#define GPC_ISR1_OFFSET 0x18
#define GPC_ISR2_OFFSET 0x1c
#define GPC_ISR3_OFFSET 0x20
#define GPC_ISR4_OFFSET 0x24
#define GPC_PGC_GPU_PGCR_OFFSET 0x260
#define GPC_PGC_CPU_PDN_OFFSET 0x2a0
#define GPC_PGC_CPU_PUPSCR_OFFSET 0x2a4
#define GPC_PGC_CPU_PDNSCR_OFFSET 0x2a8
#define UART_UCR3_OFFSET 0x88
#define UART_USR1_OFFSET 0x94
#define UART_UCR3_AWAKEN (1 << 4)
#define UART_USR1_AWAKE (1 << 4)
#define LOCAL_TWD_LOAD_OFFSET 0x0
#define LOCAL_TWD_COUNT_OFFSET 0x4
#define LOCAL_TWD_CONTROL_OFFSET 0x8
#define LOCAL_TWD_INT_OFFSET 0xc
#define ANATOP_REG_2P5_OFFSET 0x130
#define ANATOP_REG_CORE_OFFSET 0x140
static struct clk *cpu_clk;
static struct pm_platform_data *pm_data;
#if defined(CONFIG_CPU_FREQ)
extern int set_cpu_freq(int wp);
#endif
extern void mx6q_suspend(suspend_state_t state);
extern void mx6_init_irq(void);
extern unsigned int gpc_wake_irq[4];
static struct device *pm_dev;
struct clk *gpc_dvfs_clk;
static void __iomem *scu_base;
static void __iomem *gpc_base;
static void __iomem *src_base;
static void __iomem *local_twd_base;
static void __iomem *gic_dist_base;
static void __iomem *gic_cpu_base;
static void __iomem *anatop_base;
static void *suspend_iram_base;
static void (*suspend_in_iram)(suspend_state_t state,
unsigned long iram_paddr, unsigned long suspend_iram_base) = NULL;
static unsigned long iram_paddr, cpaddr;
static u32 ccm_ccr, ccm_clpcr, scu_ctrl;
static u32 gpc_imr[4], gpc_cpu_pup, gpc_cpu_pdn, gpc_cpu, gpc_gpu_pgcr;
static u32 anatop[2];
static void mx6_suspend_store(void)
{
/* save some settings before suspend */
ccm_ccr = __raw_readl(MXC_CCM_CCR);
ccm_clpcr = __raw_readl(MXC_CCM_CLPCR);
scu_ctrl = __raw_readl(scu_base + SCU_CTRL_OFFSET);
gpc_imr[0] = __raw_readl(gpc_base + GPC_IMR1_OFFSET);
gpc_imr[1] = __raw_readl(gpc_base + GPC_IMR2_OFFSET);
gpc_imr[2] = __raw_readl(gpc_base + GPC_IMR3_OFFSET);
gpc_imr[3] = __raw_readl(gpc_base + GPC_IMR4_OFFSET);
gpc_cpu_pup = __raw_readl(gpc_base + GPC_PGC_CPU_PUPSCR_OFFSET);
gpc_cpu_pdn = __raw_readl(gpc_base + GPC_PGC_CPU_PDNSCR_OFFSET);
gpc_cpu = __raw_readl(gpc_base + GPC_PGC_CPU_PDN_OFFSET);
gpc_gpu_pgcr = __raw_readl(gpc_base + GPC_PGC_GPU_PGCR_OFFSET);
anatop[0] = __raw_readl(anatop_base + ANATOP_REG_2P5_OFFSET);
anatop[1] = __raw_readl(anatop_base + ANATOP_REG_CORE_OFFSET);
}
static void mx6_suspend_restore(void)
{
/* restore settings after suspend */
__raw_writel(ccm_ccr, MXC_CCM_CCR);
__raw_writel(ccm_clpcr, MXC_CCM_CLPCR);
__raw_writel(scu_ctrl, scu_base + SCU_CTRL_OFFSET);
__raw_writel(gpc_imr[0], gpc_base + GPC_IMR1_OFFSET);
__raw_writel(gpc_imr[1], gpc_base + GPC_IMR2_OFFSET);
__raw_writel(gpc_imr[2], gpc_base + GPC_IMR3_OFFSET);
__raw_writel(gpc_imr[3], gpc_base + GPC_IMR4_OFFSET);
__raw_writel(gpc_cpu_pup, gpc_base + GPC_PGC_CPU_PUPSCR_OFFSET);
__raw_writel(gpc_cpu_pdn, gpc_base + GPC_PGC_CPU_PDNSCR_OFFSET);
__raw_writel(gpc_cpu, gpc_base + GPC_PGC_CPU_PDN_OFFSET);
__raw_writel(gpc_gpu_pgcr, gpc_base + GPC_PGC_GPU_PGCR_OFFSET);
__raw_writel(anatop[0], anatop_base + ANATOP_REG_2P5_OFFSET);
__raw_writel(anatop[1], anatop_base + ANATOP_REG_CORE_OFFSET);
}
static int mx6_suspend_enter(suspend_state_t state)
{
unsigned int wake_irq_isr[4];
struct gic_dist_state gds;
struct gic_cpu_state gcs;
wake_irq_isr[0] = __raw_readl(gpc_base +
GPC_ISR1_OFFSET) & gpc_wake_irq[0];
wake_irq_isr[1] = __raw_readl(gpc_base +
GPC_ISR1_OFFSET) & gpc_wake_irq[1];
wake_irq_isr[2] = __raw_readl(gpc_base +
GPC_ISR1_OFFSET) & gpc_wake_irq[2];
wake_irq_isr[3] = __raw_readl(gpc_base +
GPC_ISR1_OFFSET) & gpc_wake_irq[3];
if (wake_irq_isr[0] | wake_irq_isr[1] |
wake_irq_isr[2] | wake_irq_isr[3]) {
printk(KERN_INFO "There are wakeup irq pending,system resume!\n");
printk(KERN_INFO "wake_irq_isr[0-3]: 0x%x, 0x%x, 0x%x, 0x%x\n",
wake_irq_isr[0], wake_irq_isr[1],
wake_irq_isr[2], wake_irq_isr[3]);
return 0;
}
mx6_suspend_store();
switch (state) {
case PM_SUSPEND_MEM:
mxc_cpu_lp_set(ARM_POWER_OFF);
break;
case PM_SUSPEND_STANDBY:
mxc_cpu_lp_set(STOP_POWER_OFF);
break;
default:
return -EINVAL;
}
if (state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY) {
if (pm_data && pm_data->suspend_enter)
pm_data->suspend_enter();
local_flush_tlb_all();
flush_cache_all();
if (state == PM_SUSPEND_MEM) {
/* preserve gic state */
save_gic_dist_state(0, &gds);
save_gic_cpu_state(0, &gcs);
}
suspend_in_iram(state, (unsigned long)iram_paddr,
(unsigned long)suspend_iram_base);
if (state == PM_SUSPEND_MEM) {
/* restore gic registers */
restore_gic_dist_state(0, &gds);
restore_gic_cpu_state(0, &gcs);
}
mx6_suspend_restore();
if (pm_data && pm_data->suspend_exit)
pm_data->suspend_exit();
} else {
cpu_do_idle();
}
return 0;
}
/*
* Called after processes are frozen, but before we shut down devices.
*/
static int mx6_suspend_prepare(void)
{
return 0;
}
/*
* Called before devices are re-setup.
*/
static void mx6_suspend_finish(void)
{
}
/*
* Called after devices are re-setup, but before processes are thawed.
*/
static void mx6_suspend_end(void)
{
}
static int mx6_pm_valid(suspend_state_t state)
{
return (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX);
}
struct platform_suspend_ops mx6_suspend_ops = {
.valid = mx6_pm_valid,
.prepare = mx6_suspend_prepare,
.enter = mx6_suspend_enter,
.finish = mx6_suspend_finish,
.end = mx6_suspend_end,
};
static int __devinit mx6_pm_probe(struct platform_device *pdev)
{
pm_dev = &pdev->dev;
pm_data = pdev->dev.platform_data;
return 0;
}
static struct platform_driver mx6_pm_driver = {
.driver = {
.name = "imx_pm",
},
.probe = mx6_pm_probe,
};
static int __init pm_init(void)
{
scu_base = IO_ADDRESS(SCU_BASE_ADDR);
gpc_base = IO_ADDRESS(GPC_BASE_ADDR);
src_base = IO_ADDRESS(SRC_BASE_ADDR);
gic_dist_base = IO_ADDRESS(IC_DISTRIBUTOR_BASE_ADDR);
gic_cpu_base = IO_ADDRESS(IC_INTERFACES_BASE_ADDR);
local_twd_base = IO_ADDRESS(LOCAL_TWD_ADDR);
anatop_base = IO_ADDRESS(ANATOP_BASE_ADDR);
pr_info("Static Power Management for Freescale i.MX6\n");
if (platform_driver_register(&mx6_pm_driver) != 0) {
printk(KERN_ERR "mx6_pm_driver register failed\n");
return -ENODEV;
}
suspend_set_ops(&mx6_suspend_ops);
/* Move suspend routine into iRAM */
cpaddr = (unsigned long)iram_alloc(SZ_4K, &iram_paddr);
/* Need to remap the area here since we want the memory region
to be executable. */
suspend_iram_base = __arm_ioremap(iram_paddr, SZ_4K,
MT_MEMORY_NONCACHED);
pr_info("cpaddr = %x suspend_iram_base=%x\n",
(unsigned int)cpaddr, (unsigned int)suspend_iram_base);
/*
* Need to run the suspend code from IRAM as the DDR needs
* to be put into low power mode manually.
*/
memcpy((void *)cpaddr, mx6q_suspend, SZ_4K);
suspend_in_iram = (void *)suspend_iram_base;
cpu_clk = clk_get(NULL, "cpu_clk");
if (IS_ERR(cpu_clk)) {
printk(KERN_DEBUG "%s: failed to get cpu_clk\n", __func__);
return PTR_ERR(cpu_clk);
}
printk(KERN_INFO "PM driver module loaded\n");
return 0;
}
static void __exit pm_cleanup(void)
{
/* Unregister the device structure */
platform_driver_unregister(&mx6_pm_driver);
}
module_init(pm_init);
module_exit(pm_cleanup);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("PM driver");
MODULE_LICENSE("GPL");