blob: f46563241eb9ce1ff9f21981dc37dcef9edc9530 [file] [log] [blame]
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* EXYNOS Power mode
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/of.h>
#include <linux/slab.h>
#include <asm/smp_plat.h>
#include <soc/samsung/exynos-pm.h>
#include <soc/samsung/exynos-pmu.h>
#include <soc/samsung/exynos-powermode.h>
struct exynos_powermode_info {
/*
* While system boot, wakeup_mask and idle_ip_mask is intialized with
* device tree. These are used by system power mode.
*/
unsigned int num_wakeup_mask;
unsigned int *wakeup_mask_offset;
unsigned int *wakeup_mask[NUM_SYS_POWERDOWN];
};
static struct exynos_powermode_info *pm_info;
/******************************************************************************
* System power mode *
******************************************************************************/
int exynos_system_idle_enter(void)
{
int ret;
ret = exynos_prepare_sys_powerdown(SYS_SICD);
if (ret)
return ret;
exynos_pm_notify(SICD_ENTER);
return 0;
}
void exynos_system_idle_exit(int cancel)
{
exynos_pm_notify(SICD_EXIT);
exynos_wakeup_sys_powerdown(SYS_SICD, cancel);
}
#define PMU_EINT_WAKEUP_MASK 0x650
static void exynos_set_wakeupmask(enum sys_powerdown mode)
{
int i;
u64 eintmask = exynos_get_eint_wake_mask();
/* Set external interrupt mask */
exynos_pmu_write(PMU_EINT_WAKEUP_MASK, (u32)eintmask);
for (i = 0; i < pm_info->num_wakeup_mask; i++)
exynos_pmu_write(pm_info->wakeup_mask_offset[i],
pm_info->wakeup_mask[mode][i]);
}
int exynos_prepare_sys_powerdown(enum sys_powerdown mode)
{
int ret;
exynos_set_wakeupmask(mode);
ret = cal_pm_enter(mode);
if (ret)
pr_err("CAL Fail to set powermode\n");
return ret;
}
void exynos_wakeup_sys_powerdown(enum sys_powerdown mode, bool early_wakeup)
{
if (early_wakeup)
cal_pm_earlywakeup(mode);
else
cal_pm_exit(mode);
}
/******************************************************************************
* Driver initialization *
******************************************************************************/
#define for_each_syspwr_mode(mode) \
for ((mode) = 0; (mode) < NUM_SYS_POWERDOWN; (mode)++)
static int alloc_wakeup_mask(int num_wakeup_mask)
{
unsigned int mode;
pm_info->wakeup_mask_offset = kzalloc(sizeof(unsigned int)
* num_wakeup_mask, GFP_KERNEL);
if (!pm_info->wakeup_mask_offset)
return -ENOMEM;
for_each_syspwr_mode(mode) {
pm_info->wakeup_mask[mode] = kzalloc(sizeof(unsigned int)
* num_wakeup_mask, GFP_KERNEL);
if (!pm_info->wakeup_mask[mode])
goto free_reg_offset;
}
return 0;
free_reg_offset:
for_each_syspwr_mode(mode)
if (pm_info->wakeup_mask[mode])
kfree(pm_info->wakeup_mask[mode]);
kfree(pm_info->wakeup_mask_offset);
return -ENOMEM;
}
static int parsing_dt_wakeup_mask(struct device_node *np)
{
int ret;
struct device_node *root, *child;
unsigned int mode, mask_index = 0;
root = of_find_node_by_name(np, "wakeup-masks");
pm_info->num_wakeup_mask = of_get_child_count(root);
ret = alloc_wakeup_mask(pm_info->num_wakeup_mask);
if (ret)
return ret;
for_each_child_of_node(root, child) {
for_each_syspwr_mode(mode) {
ret = of_property_read_u32_index(child, "mask",
mode, &pm_info->wakeup_mask[mode][mask_index]);
if (ret)
return ret;
}
ret = of_property_read_u32(child, "mask-offset",
&pm_info->wakeup_mask_offset[mask_index]);
if (ret)
return ret;
mask_index++;
}
return 0;
}
static int __init exynos_powermode_init(void)
{
struct device_node *np;
int ret;
pm_info = kzalloc(sizeof(struct exynos_powermode_info), GFP_KERNEL);
if (pm_info == NULL) {
pr_err("%s: failed to allocate exynos_powermode_info\n", __func__);
return -ENOMEM;
}
np = of_find_node_by_name(NULL, "exynos-powermode");
ret = parsing_dt_wakeup_mask(np);
if (ret)
pr_warn("Fail to initialize the wakeup mask with err = %d\n", ret);
return 0;
}
arch_initcall(exynos_powermode_init);