755 lines
20 KiB
C
Executable file
755 lines
20 KiB
C
Executable file
/*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
*
|
|
* 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/module.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/wakeup_reason.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/smp.h>
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
#include <linux/sec_class.h>
|
|
#endif
|
|
|
|
#include <soc/samsung/exynos-pm.h>
|
|
#include <soc/samsung/exynos-pmu-if.h>
|
|
#include <soc/samsung/cal-if.h>
|
|
|
|
extern u32 exynos_eint_to_pin_num(int eint);
|
|
extern u32 exynos_eint_wake_mask_array[3];
|
|
extern void log_wakeup_reason(int irq);
|
|
|
|
#define EXYNOS_EINT_PEND(b, x) ((b) + 0xA00 + (((x) >> 3) * 4))
|
|
|
|
static struct exynos_pm_info *pm_info;
|
|
static struct exynos_pm_dbg *pm_dbg;
|
|
|
|
static void exynos_print_wakeup_sources(int irq, const char *name)
|
|
{
|
|
struct irq_desc *desc;
|
|
|
|
if (irq < 0) {
|
|
if (name)
|
|
pr_info("PM: Resume caused by SYSINT: %s\n", name);
|
|
|
|
return;
|
|
}
|
|
|
|
desc = irq_to_desc(irq);
|
|
if (desc && desc->action && desc->action->name)
|
|
pr_info("PM: Resume caused by IRQ %d, %s\n", irq,
|
|
desc->action->name);
|
|
else
|
|
pr_info("PM: Resume caused by IRQ %d\n", irq);
|
|
}
|
|
|
|
static void exynos_show_wakeup_reason_eint(void)
|
|
{
|
|
int bit;
|
|
int i, size;
|
|
long unsigned int ext_int_pend;
|
|
u64 eint_wakeup_mask;
|
|
bool found = 0;
|
|
unsigned int val0 = 0, val1 = 0;
|
|
|
|
exynos_pmu_read(pm_info->eint_wakeup_mask_offset[0], &val0);
|
|
exynos_pmu_read(pm_info->eint_wakeup_mask_offset[1], &val1);
|
|
eint_wakeup_mask = val1;
|
|
eint_wakeup_mask = ((eint_wakeup_mask << 32) | val0);
|
|
|
|
for (i = 0, size = 8; i < pm_info->num_eint; i += size) {
|
|
ext_int_pend =
|
|
__raw_readl(EXYNOS_EINT_PEND(pm_info->eint_base, i));
|
|
|
|
for_each_set_bit(bit, &ext_int_pend, size) {
|
|
u32 gpio;
|
|
int irq;
|
|
|
|
if (eint_wakeup_mask & (1 << (i + bit)))
|
|
continue;
|
|
|
|
gpio = exynos_eint_to_pin_num(i + bit);
|
|
irq = gpio_to_irq(gpio);
|
|
|
|
exynos_print_wakeup_sources(irq, NULL);
|
|
found = 1;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
pr_info("%s Resume caused by unknown EINT\n", EXYNOS_PM_PREFIX);
|
|
}
|
|
|
|
static void exynos_show_wakeup_registers(unsigned int wakeup_stat)
|
|
{
|
|
int i, size;
|
|
|
|
pr_info("WAKEUP_STAT:\n");
|
|
for (i = 0; i < pm_info->num_wakeup_stat; i++) {
|
|
exynos_pmu_read(pm_info->wakeup_stat_offset[i], &wakeup_stat);
|
|
pr_info("0x%08x\n", wakeup_stat);
|
|
}
|
|
|
|
pr_info("EINT_PEND: ");
|
|
for (i = 0, size = 8; i < pm_info->num_eint; i += size)
|
|
pr_info("0x%02x ", __raw_readl(EXYNOS_EINT_PEND(pm_info->eint_base, i)));
|
|
}
|
|
|
|
static void exynos_show_wakeup_reason_sysint(unsigned int stat,
|
|
struct wakeup_stat_name *ws_names)
|
|
{
|
|
int bit;
|
|
unsigned long lstat = stat;
|
|
const char *name;
|
|
|
|
for_each_set_bit(bit, &lstat, 32) {
|
|
name = ws_names->name[bit];
|
|
|
|
if (!name)
|
|
continue;
|
|
|
|
exynos_print_wakeup_sources(-1, name);
|
|
}
|
|
}
|
|
|
|
static int exynos_show_wakeup_reason_detail(unsigned int wakeup_stat)
|
|
{
|
|
int i;
|
|
unsigned int wss;
|
|
|
|
if (wakeup_stat & (1 << pm_info->wakeup_stat_eint))
|
|
exynos_show_wakeup_reason_eint();
|
|
|
|
if (unlikely(!pm_info->ws_names))
|
|
return 0;
|
|
|
|
for (i = 0; i < pm_info->num_wakeup_stat; i++) {
|
|
if (i == 0)
|
|
wss = wakeup_stat & ~(1 << pm_info->wakeup_stat_eint);
|
|
else
|
|
exynos_pmu_read(pm_info->wakeup_stat_offset[i], &wss);
|
|
|
|
if (!wss)
|
|
continue;
|
|
|
|
exynos_show_wakeup_reason_sysint(wss, &pm_info->ws_names[i]);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void exynos_show_wakeup_reason(bool sleep_abort)
|
|
{
|
|
unsigned int wakeup_stat;
|
|
int i, size;
|
|
|
|
if (sleep_abort) {
|
|
pr_info("%s early wakeup! Dumping pending registers...\n", EXYNOS_PM_PREFIX);
|
|
|
|
pr_info("EINT_PEND:\n");
|
|
for (i = 0, size = 8; i < pm_info->num_eint; i += size)
|
|
pr_info("0x%x\n", __raw_readl(EXYNOS_EINT_PEND(pm_info->eint_base, i)));
|
|
|
|
pr_info("GIC_PEND:\n");
|
|
for (i = 0; i < pm_info->num_gic; i++)
|
|
pr_info("GICD_ISPENDR[%d] = 0x%x\n", i, __raw_readl(pm_info->gic_base + i*4));
|
|
|
|
pr_info("%s done.\n", EXYNOS_PM_PREFIX);
|
|
return ;
|
|
}
|
|
|
|
if (!pm_info->num_wakeup_stat)
|
|
return;
|
|
|
|
exynos_pmu_read(pm_info->wakeup_stat_offset[0], &wakeup_stat);
|
|
exynos_show_wakeup_registers(wakeup_stat);
|
|
|
|
if (exynos_show_wakeup_reason_detail(wakeup_stat))
|
|
return;
|
|
|
|
if (wakeup_stat & (1 << pm_info->wakeup_stat_rtc))
|
|
pr_info("%s Resume caused by RTC alarm\n", EXYNOS_PM_PREFIX);
|
|
else if (wakeup_stat & (1 << pm_info->wakeup_stat_eint))
|
|
exynos_show_wakeup_reason_eint();
|
|
else {
|
|
for (i = 0; i < pm_info->num_wakeup_stat; i++) {
|
|
exynos_pmu_read(pm_info->wakeup_stat_offset[i], &wakeup_stat);
|
|
pr_info("%s Resume caused by wakeup%d_stat 0x%08x\n",
|
|
EXYNOS_PM_PREFIX, i + 1, wakeup_stat);
|
|
|
|
}
|
|
}
|
|
}
|
|
extern u32 otg_is_connect(void);
|
|
static void exynos_set_wakeupmask(enum sys_powerdown mode)
|
|
{
|
|
int i;
|
|
u32 wakeup_int_en = 0;
|
|
|
|
/* Set external interrupt mask */
|
|
for (i = 0; i < pm_info->num_eint_wakeup_mask; i++)
|
|
exynos_pmu_write(pm_info->eint_wakeup_mask_offset[i], exynos_eint_wake_mask_array[i]);
|
|
|
|
for (i = 0; i < pm_info->num_wakeup_int_en; i++) {
|
|
exynos_pmu_write(pm_info->wakeup_stat_offset[i], 0);
|
|
wakeup_int_en = pm_info->wakeup_int_en[i];
|
|
|
|
if (otg_is_connect() == 2 || otg_is_connect() == 0)
|
|
wakeup_int_en&= ~pm_info->usbl2_wakeup_int_en[i];
|
|
|
|
exynos_pmu_write(pm_info->wakeup_int_en_offset[i], wakeup_int_en);
|
|
}
|
|
|
|
|
|
__raw_writel(pm_info->vgpio_wakeup_inten,
|
|
pm_info->vgpio2pmu_base + pm_info->vgpio_inten_offset);
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static void exynos_wakeup_sys_powerdown(enum sys_powerdown mode, bool early_wakeup)
|
|
{
|
|
if (early_wakeup)
|
|
cal_pm_earlywakeup(mode);
|
|
else
|
|
cal_pm_exit(mode);
|
|
|
|
__raw_writel(0, pm_info->vgpio2pmu_base + pm_info->vgpio_inten_offset);
|
|
|
|
}
|
|
|
|
static void print_dbg_subsystem(void)
|
|
{
|
|
int i = 0;
|
|
u32 reg;
|
|
|
|
if (!pm_info->num_dbg_subsystem)
|
|
return;
|
|
|
|
pr_info("%s Debug Subsystem:\n", EXYNOS_PM_PREFIX);
|
|
|
|
for (i = 0; i < pm_info->num_dbg_subsystem; i++) {
|
|
exynos_pmu_read(pm_info->dbg_subsystem_offset[i], ®);
|
|
|
|
pr_info("%s: 0x%08x\n", pm_info->dbg_subsystem_name[i], reg);
|
|
}
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PINCTRL_SEC_GPIO_DVS)
|
|
extern void gpio_dvs_check_sleepgpio(void);
|
|
#endif /* CONFIG_PINCTRL_SEC_GPIO_DVS */
|
|
|
|
static int exynos_pm_syscore_suspend(void)
|
|
{
|
|
#ifdef CONFIG_CP_PMUCAL
|
|
if (!exynos_check_cp_status()) {
|
|
pr_info("%s %s: sleep canceled by CP reset \n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_PINCTRL_SEC_GPIO_DVS)
|
|
/************************ Caution !!! ****************************/
|
|
/* This function must be located in appropriate SLEEP position
|
|
* in accordance with the specification of each BB vendor.
|
|
*/
|
|
/************************ Caution !!! ****************************/
|
|
gpio_dvs_check_sleepgpio();
|
|
#endif /* CONFIG_PINCTRL_SEC_GPIO_DVS */
|
|
|
|
pm_info->is_pcieon_suspend = false;
|
|
if (pm_info->pcieon_suspend_available) {
|
|
if (!IS_ERR_OR_NULL(pm_info->pcie_is_connect))
|
|
pm_info->is_pcieon_suspend = !!pm_info->pcie_is_connect();
|
|
}
|
|
|
|
if (pm_info->is_pcieon_suspend) {
|
|
exynos_prepare_sys_powerdown(pm_info->pcieon_suspend_mode_idx);
|
|
pr_info("%s %s: Enter Suspend scenario. pcieon_mode_idx = %d)\n",
|
|
EXYNOS_PM_PREFIX,__func__, pm_info->pcieon_suspend_mode_idx);
|
|
} else {
|
|
exynos_prepare_sys_powerdown(pm_info->suspend_mode_idx);
|
|
pr_info("%s %s: Enter Suspend scenario. suspend_mode_idx = %d)\n",
|
|
EXYNOS_PM_PREFIX,__func__, pm_info->suspend_mode_idx);
|
|
}
|
|
|
|
/* Send an IPI if test_early_wakeup flag is set */
|
|
// if (pm_dbg->test_early_wakeup)
|
|
// arch_send_call_function_single_ipi(0);
|
|
|
|
pm_dbg->mifdn_cnt_prev = acpm_get_mifdn_count();
|
|
pm_info->apdn_cnt_prev = acpm_get_apsocdn_count();
|
|
pm_dbg->mif_req = acpm_get_mif_request();
|
|
|
|
pm_dbg->mifdn_early_wakeup_prev = acpm_get_early_wakeup_count();
|
|
|
|
pr_info("%s: prev mif_count:%d, apsoc_count:%d, seq_early_wakeup_count:%d\n",
|
|
EXYNOS_PM_PREFIX, pm_dbg->mifdn_cnt_prev,
|
|
pm_info->apdn_cnt_prev, pm_dbg->mifdn_early_wakeup_prev);
|
|
|
|
exynos_flexpmu_dbg_set_sleep_req();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_pm_syscore_resume(void)
|
|
{
|
|
pm_dbg->mifdn_cnt = acpm_get_mifdn_count();
|
|
pm_info->apdn_cnt = acpm_get_apsocdn_count();
|
|
pm_dbg->mifdn_early_wakeup_cnt = acpm_get_early_wakeup_count();
|
|
|
|
pr_info("%s: post mif_count:%d, apsoc_count:%d, seq_early_wakeup_count:%d\n",
|
|
EXYNOS_PM_PREFIX, pm_dbg->mifdn_cnt,
|
|
pm_info->apdn_cnt, pm_dbg->mifdn_early_wakeup_cnt);
|
|
|
|
if (pm_info->apdn_cnt == pm_info->apdn_cnt_prev) {
|
|
pm_info->is_early_wakeup = true;
|
|
pr_info("%s %s: return to originator\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
} else
|
|
pm_info->is_early_wakeup = false;
|
|
|
|
if (pm_dbg->mifdn_early_wakeup_cnt != pm_dbg->mifdn_early_wakeup_prev)
|
|
pr_debug("%s: Sequence early wakeup\n", EXYNOS_PM_PREFIX);
|
|
|
|
if (pm_dbg->mifdn_cnt == pm_dbg->mifdn_cnt_prev)
|
|
pr_info("%s: MIF blocked. MIF request Mster was 0x%x\n",
|
|
EXYNOS_PM_PREFIX, pm_dbg->mif_req);
|
|
else
|
|
pr_info("%s: MIF down. cur_count: %d, acc_count: %d\n",
|
|
EXYNOS_PM_PREFIX, pm_dbg->mifdn_cnt - pm_dbg->mifdn_cnt_prev, pm_dbg->mifdn_cnt);
|
|
|
|
if (pm_info->is_pcieon_suspend)
|
|
exynos_wakeup_sys_powerdown(pm_info->pcieon_suspend_mode_idx, pm_info->is_early_wakeup);
|
|
else
|
|
exynos_wakeup_sys_powerdown(pm_info->suspend_mode_idx, pm_info->is_early_wakeup);
|
|
|
|
print_dbg_subsystem();
|
|
exynos_show_wakeup_reason(pm_info->is_early_wakeup);
|
|
exynos_flexpmu_dbg_clr_wakeup_req();
|
|
|
|
if (!pm_info->is_early_wakeup)
|
|
pr_debug("%s %s: post sleep, preparing to return\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
}
|
|
|
|
static struct syscore_ops exynos_pm_syscore_ops = {
|
|
.suspend = exynos_pm_syscore_suspend,
|
|
.resume = exynos_pm_syscore_resume,
|
|
};
|
|
|
|
int register_pcie_is_connect(u32 (*func)(void))
|
|
{
|
|
if(func) {
|
|
pm_info->pcie_is_connect = func;
|
|
pr_info("Registered pcie_is_connect func\n");
|
|
return 0;
|
|
} else {
|
|
pr_err("%s :function pointer is NULL \n", __func__);
|
|
return -ENXIO;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_pcie_is_connect);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int wake_lock_get(void *data, unsigned long long *val)
|
|
{
|
|
*val = (unsigned long long)pm_info->is_stay_awake;
|
|
return 0;
|
|
}
|
|
|
|
static int wake_lock_set(void *data, unsigned long long val)
|
|
{
|
|
bool before = pm_info->is_stay_awake;
|
|
|
|
pm_info->is_stay_awake = (bool)val;
|
|
|
|
if (before != pm_info->is_stay_awake) {
|
|
if (pm_info->is_stay_awake)
|
|
__pm_stay_awake(pm_info->ws);
|
|
else
|
|
__pm_relax(pm_info->ws);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(wake_lock_fops, wake_lock_get, wake_lock_set, "%llu\n");
|
|
|
|
static void exynos_pm_debugfs_init(void)
|
|
{
|
|
struct dentry *root;
|
|
|
|
root = debugfs_create_dir("exynos-pm", NULL);
|
|
if (!root) {
|
|
pr_err("%s %s: could't create debugfs dir\n", EXYNOS_PM_PREFIX, __func__);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
d = debugfs_create_file("test_early_wakeup", 0644, root, NULL, &pm_dbg->test_early_wakeup);
|
|
if (!d) {
|
|
pr_err("%s %s: could't create debugfs test_early_wakeup\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (pm_info->ws)
|
|
debugfs_create_file("wake_lock", 0644, root, NULL, &wake_lock_fops);
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
static ssize_t asv_info_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int count = 0;
|
|
|
|
/* Set asv group info to buf */
|
|
#if defined(CONFIG_SOC_S5E9925)
|
|
count += sprintf(&buf[count], "%d ", asv_ids_information(tg));
|
|
count += sprintf(&buf[count], "%03x ", asv_ids_information(mg));
|
|
count += sprintf(&buf[count], "%03x ", asv_ids_information(g3dg));
|
|
count += sprintf(&buf[count], "%u ", asv_ids_information(mids));
|
|
count += sprintf(&buf[count], "%u ", asv_ids_information(gids));
|
|
#elif defined(CONFIG_SOC_S5E8825)
|
|
count += sprintf(&buf[count], "%d ", asv_ids_information(tg));
|
|
count += sprintf(&buf[count], "%03x ", asv_ids_information(bg));
|
|
count += sprintf(&buf[count], "%03x ", asv_ids_information(g3dg));
|
|
count += sprintf(&buf[count], "%u ", asv_ids_information(bids));
|
|
count += sprintf(&buf[count], "%u ", asv_ids_information(gids));
|
|
#endif
|
|
count += sprintf(&buf[count], "\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(asv_info);
|
|
#endif /* CONFIG_SEC_FACTORY */
|
|
|
|
static void parse_dt_wakeup_stat_names(struct device_node *np)
|
|
{
|
|
struct device_node *root, *child;
|
|
int ret;
|
|
int size, n, idx = 0;
|
|
|
|
root = of_find_node_by_name(np, "wakeup_stats");
|
|
n = of_get_child_count(root);
|
|
|
|
if (pm_info->num_wakeup_stat != n || !n) {
|
|
pr_err("%s: failed to get wakeup_stats(%d)\n", __func__, n);
|
|
return;
|
|
}
|
|
|
|
pm_info->ws_names = kzalloc(sizeof(*pm_info->ws_names) * n, GFP_KERNEL);
|
|
if (!pm_info->ws_names)
|
|
return;
|
|
|
|
for_each_child_of_node(root, child) {
|
|
size = of_property_count_strings(child, "ws-name");
|
|
if (size <= 0 || size > 32) {
|
|
pr_err("%s: failed to get wakeup_stat name cnt(%d)\n",
|
|
__func__, size);
|
|
return;
|
|
}
|
|
|
|
ret = of_property_read_string_array(child, "ws-name",
|
|
pm_info->ws_names[idx].name, size);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to read wakeup_stat name(%d)\n",
|
|
__func__, ret);
|
|
return;
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
static void parse_dt_debug_subsystem(struct device_node *np)
|
|
{
|
|
struct device_node *child;
|
|
int ret;
|
|
int size_name, size_offset = 0;
|
|
|
|
child = of_find_node_by_name(np, "debug_subsystem");
|
|
|
|
if (!child) {
|
|
pr_err("%s %s: unable to get debug_subsystem value from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
return;
|
|
}
|
|
|
|
size_name = of_property_count_strings(child, "sfr-name");
|
|
size_offset = of_property_count_u32_elems(child, "sfr-offset");
|
|
|
|
if (size_name != size_offset) {
|
|
pr_err("%s %s: size mismatch(%d, %d)\n",
|
|
EXYNOS_PM_PREFIX, __func__, size_name, size_offset);
|
|
return;
|
|
}
|
|
|
|
pm_info->dbg_subsystem_name = kcalloc(size_name, sizeof(const char *), GFP_KERNEL);
|
|
if (!pm_info->dbg_subsystem_name)
|
|
return;
|
|
|
|
pm_info->dbg_subsystem_offset = kcalloc(size_offset, sizeof(unsigned int), GFP_KERNEL);
|
|
if (!pm_info->dbg_subsystem_offset)
|
|
goto free_name;
|
|
|
|
ret = of_property_read_string_array(child, "sfr-name",
|
|
pm_info->dbg_subsystem_name, size_name);
|
|
if (ret < 0) {
|
|
pr_err("%s %s: failed to get debug_subsystem name from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
goto free_offset;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(child, "sfr-offset",
|
|
pm_info->dbg_subsystem_offset, size_offset);
|
|
if (ret < 0) {
|
|
pr_err("%s %s: failed to get debug_subsystem offset from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
goto free_offset;
|
|
}
|
|
|
|
pm_info->num_dbg_subsystem = size_name;
|
|
return;
|
|
|
|
free_offset:
|
|
kfree(pm_info->dbg_subsystem_offset);
|
|
free_name:
|
|
kfree(pm_info->dbg_subsystem_name);
|
|
pm_info->num_dbg_subsystem = 0;
|
|
}
|
|
|
|
static int exynos_pm_drvinit(void)
|
|
{
|
|
int ret;
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
struct device *dev;
|
|
#endif
|
|
|
|
pm_info = kzalloc(sizeof(struct exynos_pm_info), GFP_KERNEL);
|
|
if (pm_info == NULL) {
|
|
pr_err("%s %s: failed to allocate memory for exynos_pm_info\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
pm_dbg = kzalloc(sizeof(struct exynos_pm_dbg), GFP_KERNEL);
|
|
if (pm_dbg == NULL) {
|
|
pr_err("%s %s: failed to allocate memory for exynos_pm_dbg\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
if (of_have_populated_dt()) {
|
|
struct device_node *np;
|
|
unsigned int wake_lock = 0;
|
|
np = of_find_compatible_node(NULL, NULL, "samsung,exynos-pm");
|
|
if (!np) {
|
|
pr_err("%s %s: unabled to find compatible node (%s)\n",
|
|
EXYNOS_PM_PREFIX, __func__, "samsung,exynos-pm");
|
|
BUG();
|
|
}
|
|
|
|
pm_info->eint_base = of_iomap(np, 0);
|
|
if (!pm_info->eint_base) {
|
|
pr_err("%s %s: unabled to ioremap EINT base address\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
pm_info->gic_base = of_iomap(np, 1);
|
|
if (!pm_info->gic_base) {
|
|
pr_err("%s %s: unbaled to ioremap GIC base address\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
pm_info->vgpio2pmu_base = of_iomap(np, 2);
|
|
if (!pm_info->vgpio2pmu_base) {
|
|
pr_err("%s %s: unbaled to ioremap VGPIO2PMU base address\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "num-eint", &pm_info->num_eint);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the number of eint from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "num-gic", &pm_info->num_gic);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the number of gic from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "wakeup-stat-eint", &pm_info->wakeup_stat_eint);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the eint bit file of WAKEUP_STAT from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "wakeup-stat-rtc", &pm_info->wakeup_stat_rtc);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the rtc bit file of WAKEUP_STAT from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "suspend_mode_idx", &pm_info->suspend_mode_idx);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get suspend_mode_idx from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_count_u32_elems(np, "wakeup_stat_offset");
|
|
if (!ret) {
|
|
pr_err("%s %s: unabled to get wakeup_stat value from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
} else if (ret > 0) {
|
|
pm_info->num_wakeup_stat = ret;
|
|
pm_info->wakeup_stat_offset = kzalloc(sizeof(unsigned int) * ret, GFP_KERNEL);
|
|
of_property_read_u32_array(np, "wakeup_stat_offset", pm_info->wakeup_stat_offset, ret);
|
|
}
|
|
|
|
parse_dt_wakeup_stat_names(np);
|
|
parse_dt_debug_subsystem(np);
|
|
|
|
ret = of_property_count_u32_elems(np, "wakeup_int_en_offset");
|
|
if (!ret) {
|
|
pr_err("%s %s: unabled to get wakeup_int_en_offset value from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
} else if (ret > 0) {
|
|
pm_info->num_wakeup_int_en = ret;
|
|
pm_info->wakeup_int_en_offset = kzalloc(sizeof(unsigned int) * ret, GFP_KERNEL);
|
|
of_property_read_u32_array(np, "wakeup_int_en_offset", pm_info->wakeup_int_en_offset, ret);
|
|
}
|
|
|
|
ret = of_property_count_u32_elems(np, "wakeup_int_en");
|
|
if (!ret) {
|
|
pr_err("%s %s: unabled to get wakeup_int_en value from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
} else if (ret > 0) {
|
|
pm_info->wakeup_int_en = kzalloc(sizeof(unsigned int) * ret, GFP_KERNEL);
|
|
of_property_read_u32_array(np, "wakeup_int_en", pm_info->wakeup_int_en, ret);
|
|
}
|
|
|
|
ret = of_property_count_u32_elems(np, "usbl2_wakeup_int_en");
|
|
if (!ret) {
|
|
pr_err("%s %s: dose not support usbl2 sleep\n", EXYNOS_PM_PREFIX, __func__);
|
|
} else if (ret > 0) {
|
|
pm_info->usbl2_wakeup_int_en = kzalloc(sizeof(unsigned int) * ret, GFP_KERNEL);
|
|
of_property_read_u32_array(np, "usbl2_wakeup_int_en", pm_info->usbl2_wakeup_int_en, ret);
|
|
}
|
|
|
|
ret = of_property_count_u32_elems(np, "eint_wakeup_mask_offset");
|
|
if (!ret) {
|
|
pr_err("%s %s: unabled to get eint_wakeup_mask_offset value from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
} else if (ret > 0) {
|
|
pm_info->num_eint_wakeup_mask = ret;
|
|
pm_info->eint_wakeup_mask_offset = kzalloc(sizeof(unsigned int) * ret, GFP_KERNEL);
|
|
of_property_read_u32_array(np, "eint_wakeup_mask_offset", pm_info->eint_wakeup_mask_offset, ret);
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "vgpio_wakeup_inten", &pm_info->vgpio_wakeup_inten);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the number of eint from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "vgpio_wakeup_inten_offset", &pm_info->vgpio_inten_offset);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get the number of eint from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "wake_lock", &wake_lock);
|
|
if (ret) {
|
|
pr_info("%s %s: unabled to get wake_lock from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
} else {
|
|
pm_info->ws = wakeup_source_register(NULL, "exynos-pm");
|
|
if (!pm_info->ws)
|
|
BUG();
|
|
|
|
pm_info->is_stay_awake = (bool)wake_lock;
|
|
|
|
if (pm_info->is_stay_awake)
|
|
__pm_stay_awake(pm_info->ws);
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "pcieon_suspend_available", &pm_info->pcieon_suspend_available);
|
|
if (ret) {
|
|
pr_info("%s %s: Not support pcieon_suspend mode\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
} else {
|
|
ret = of_property_read_u32(np, "pcieon_suspend_mode_idx", &pm_info->pcieon_suspend_mode_idx);
|
|
if (ret) {
|
|
pr_err("%s %s: unabled to get pcieon_suspend_mode_idx from DT\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
} else {
|
|
pr_err("%s %s: failed to have populated device tree\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
BUG();
|
|
}
|
|
|
|
register_syscore_ops(&exynos_pm_syscore_ops);
|
|
#ifdef CONFIG_DEBUG_FS
|
|
exynos_pm_debugfs_init();
|
|
#endif
|
|
#if defined(CONFIG_SEC_FACTORY)
|
|
dev = sec_device_create(NULL, "asv");
|
|
BUG_ON(!dev);
|
|
if (IS_ERR(dev))
|
|
pr_err("%s %s: failed to create sec device\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
|
|
if (device_create_file(dev, &dev_attr_asv_info) < 0)
|
|
pr_err("%s %s: failed to create sysfs file\n",
|
|
EXYNOS_PM_PREFIX, __func__);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
arch_initcall(exynos_pm_drvinit);
|
|
|
|
MODULE_LICENSE("GPL");
|