/* * 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 #include #include #include #include #include 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);