1046 lines
27 KiB
C
1046 lines
27 KiB
C
/*
|
|
* 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/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/psci.h>
|
|
#include <linux/cpuidle_profiler.h>
|
|
#include <linux/exynos-ss.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <asm/smp_plat.h>
|
|
|
|
#include <soc/samsung/exynos-pm.h>
|
|
#include <soc/samsung/exynos-pmu.h>
|
|
#include <soc/samsung/exynos-powermode.h>
|
|
|
|
#if defined(CONFIG_SEC_PM) && defined(CONFIG_MUIC_NOTIFIER)
|
|
#include <linux/muic/muic.h>
|
|
#include <linux/muic/muic_notifier.h>
|
|
#if defined(CONFIG_CCIC_NOTIFIER)
|
|
#include <linux/ccic/ccic_notifier.h>
|
|
#endif /* CONFIG_CCIC_NOTIFIER */
|
|
#endif /* CONFIG_SEC_PM && CONFIG_MUIC_NOTIFIER */
|
|
|
|
struct exynos_powermode_info {
|
|
unsigned int cpd_residency; /* target residency of cpd */
|
|
unsigned int sicd_residency; /* target residency of sicd */
|
|
|
|
struct cpumask c2_mask; /* per cpu c2 status */
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
unsigned int cpd_enabled; /* CPD activation */
|
|
#else
|
|
unsigned int *cpd_enabled;
|
|
unsigned int num_cpd_enabled;
|
|
#endif
|
|
int cpd_blocking; /* blocking CPD */
|
|
|
|
int sicd_enabled; /* SICD activation */
|
|
bool sicd_entered;
|
|
|
|
/*
|
|
* 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];
|
|
int idle_ip_mask[NUM_SYS_POWERDOWN][NUM_IDLE_IP];
|
|
unsigned int eint_wakeup_mask;
|
|
};
|
|
|
|
static struct exynos_powermode_info *powermode_info;
|
|
|
|
/******************************************************************************
|
|
* CAL interfaces *
|
|
******************************************************************************/
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
#define linear_phycpu(mpidr) \
|
|
((MPIDR_AFFINITY_LEVEL(mpidr, 1) << 2) \
|
|
| MPIDR_AFFINITY_LEVEL(mpidr, 0))
|
|
#else
|
|
#define CLUSTER0_CORES_CNT (2)
|
|
#define CLUSTER1_CORES_CNT (4)
|
|
unsigned int linear_phycpu(unsigned int mpidr)
|
|
{
|
|
unsigned int cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
|
|
unsigned int cpuid = 0;
|
|
|
|
/* It's base on the pmucal_cpu_xxx.h */
|
|
switch (cluster) {
|
|
case 2:
|
|
cpuid += CLUSTER1_CORES_CNT;
|
|
case 1:
|
|
cpuid += CLUSTER0_CORES_CNT;
|
|
case 0:
|
|
cpuid += MPIDR_AFFINITY_LEVEL(mpidr, 0);
|
|
break;
|
|
}
|
|
|
|
return cpuid;
|
|
}
|
|
#endif
|
|
|
|
#if !defined(CONFIG_SOC_EXYNOS7885) || defined(CONFIG_ARM64_EXYNOS_CPUIDLE)
|
|
static unsigned int get_cluster_id(unsigned int cpu)
|
|
{
|
|
return MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1);
|
|
}
|
|
#endif
|
|
|
|
static void cpu_enable(unsigned int cpu)
|
|
{
|
|
unsigned int mpidr = cpu_logical_map(cpu);
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
unsigned int phycpu = get_cluster_id(cpu) ?
|
|
linear_phycpu(mpidr) - 2 : linear_phycpu(mpidr);
|
|
#else
|
|
unsigned int phycpu = linear_phycpu(mpidr);
|
|
#endif
|
|
cal_cpu_enable(phycpu);
|
|
}
|
|
|
|
static void cpu_disable(unsigned int cpu)
|
|
{
|
|
unsigned int mpidr = cpu_logical_map(cpu);
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
unsigned int phycpu = get_cluster_id(cpu) ?
|
|
linear_phycpu(mpidr) - 2 : linear_phycpu(mpidr);
|
|
#else
|
|
unsigned int phycpu = linear_phycpu(mpidr);
|
|
#endif
|
|
cal_cpu_disable(phycpu);
|
|
}
|
|
|
|
static void cluster_enable(unsigned int cpu)
|
|
{
|
|
unsigned int mpidr = cpu_logical_map(cpu);
|
|
unsigned int phycluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
|
|
|
|
cal_cluster_enable(phycluster);
|
|
}
|
|
|
|
static void cluster_disable(unsigned int cpu)
|
|
{
|
|
unsigned int mpidr = cpu_logical_map(cpu);
|
|
unsigned int phycluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
|
|
|
|
cal_cluster_disable(phycluster);
|
|
}
|
|
|
|
#ifdef CONFIG_ARM64_EXYNOS_CPUIDLE
|
|
|
|
/******************************************************************************
|
|
* IDLE_IP *
|
|
******************************************************************************/
|
|
#define PMU_IDLE_IP_BASE 0x03E0
|
|
#define PMU_IDLE_IP_MASK_BASE 0x03F0
|
|
#define PMU_IDLE_IP(x) (PMU_IDLE_IP_BASE + (x * 0x4))
|
|
#define PMU_IDLE_IP_MASK(x) (PMU_IDLE_IP_MASK_BASE + (x * 0x4))
|
|
|
|
static int exynos_check_idle_ip_stat(int mode, int reg_index)
|
|
{
|
|
unsigned int val, mask;
|
|
int ret;
|
|
|
|
exynos_pmu_read(PMU_IDLE_IP(reg_index), &val);
|
|
mask = powermode_info->idle_ip_mask[mode][reg_index];
|
|
|
|
ret = (val & ~mask) == ~mask ? 0 : -EBUSY;
|
|
|
|
if (ret) {
|
|
/*
|
|
* Profile non-idle IP using idle_ip.
|
|
* A bit of idle-ip equals 0, it means non-idle. But then, if
|
|
* same bit of idle-ip-mask is 1, PMU does not see this bit.
|
|
* To know what IP blocks to enter system power mode, suppose
|
|
* below example: (express only 8 bits)
|
|
*
|
|
* idle-ip : 1 0 1 1 0 0 1 0
|
|
* mask : 1 1 0 0 1 0 0 1
|
|
*
|
|
* First, clear masked idle-ip bit.
|
|
*
|
|
* idle-ip : 1 0 1 1 0 0 1 0
|
|
* ~mask : 0 0 1 1 0 1 1 0
|
|
* -------------------------- (AND)
|
|
* idle-ip' : 0 0 1 1 0 0 1 0
|
|
*
|
|
* In upper case, only idle-ip[2] is not in idle. Calculates
|
|
* as follows, then we can get the non-idle IP easily.
|
|
*
|
|
* idle-ip' : 0 0 1 1 0 0 1 0
|
|
* ~mask : 0 0 1 1 0 1 1 0
|
|
*--------------------------- (XOR)
|
|
* 0 0 0 0 0 1 0 0
|
|
*/
|
|
cpuidle_profile_collect_idle_ip(mode, reg_index,
|
|
((val & ~mask) ^ ~mask));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int syspwr_mode_available(unsigned int mode)
|
|
{
|
|
int index;
|
|
|
|
for_each_idle_ip(index)
|
|
if (exynos_check_idle_ip_stat(mode, index))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(idle_ip_mask_lock);
|
|
static void exynos_set_idle_ip_mask(enum sys_powerdown mode)
|
|
{
|
|
int i;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&idle_ip_mask_lock, flags);
|
|
for_each_idle_ip(i)
|
|
exynos_pmu_write(PMU_IDLE_IP_MASK(i),
|
|
powermode_info->idle_ip_mask[mode][i]);
|
|
spin_unlock_irqrestore(&idle_ip_mask_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* There are 4 IDLE_IP registers in PMU, IDLE_IP therefore supports 128 index,
|
|
* 0 from 127. To access the IDLE_IP register, convert_idle_ip_index() converts
|
|
* idle_ip index to register index and bit in regster. For example, idle_ip index
|
|
* 33 converts to IDLE_IP1[1]. convert_idle_ip_index() returns register index
|
|
* and ships bit in register to *ip_index.
|
|
*/
|
|
static int convert_idle_ip_index(int *ip_index)
|
|
{
|
|
int reg_index;
|
|
|
|
reg_index = *ip_index / IDLE_IP_REG_SIZE;
|
|
*ip_index = *ip_index % IDLE_IP_REG_SIZE;
|
|
|
|
return reg_index;
|
|
}
|
|
|
|
static void idle_ip_unmask(int mode, int ip_index)
|
|
{
|
|
int reg_index = convert_idle_ip_index(&ip_index);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&idle_ip_mask_lock, flags);
|
|
powermode_info->idle_ip_mask[mode][reg_index] &= ~(0x1 << ip_index);
|
|
spin_unlock_irqrestore(&idle_ip_mask_lock, flags);
|
|
}
|
|
|
|
static int is_idle_ip_index_used(struct device_node *node, int ip_index)
|
|
{
|
|
int proplen;
|
|
int ref_idle_ip[IDLE_IP_MAX_INDEX];
|
|
int i;
|
|
|
|
proplen = of_property_count_u32_elems(node, "ref-idle-ip");
|
|
|
|
if (proplen <= 0)
|
|
return false;
|
|
|
|
if (!of_property_read_u32_array(node, "ref-idle-ip",
|
|
ref_idle_ip, proplen)) {
|
|
for (i = 0; i < proplen; i++)
|
|
if (ip_index == ref_idle_ip[i])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void exynos_create_idle_ip_mask(int ip_index)
|
|
{
|
|
struct device_node *root = of_find_node_by_path("/exynos-powermode/idle_ip_mask");
|
|
struct device_node *node;
|
|
|
|
for_each_child_of_node(root, node) {
|
|
int mode;
|
|
|
|
if (of_property_read_u32(node, "mode-index", &mode))
|
|
continue;
|
|
|
|
if (is_idle_ip_index_used(node, ip_index))
|
|
idle_ip_unmask(mode, ip_index);
|
|
}
|
|
}
|
|
|
|
int exynos_get_idle_ip_index(const char *ip_name)
|
|
{
|
|
struct device_node *np = of_find_node_by_name(NULL, "exynos-powermode");
|
|
int ip_index;
|
|
|
|
ip_index = of_property_match_string(np, "idle-ip", ip_name);
|
|
if (ip_index < 0) {
|
|
pr_err("%s: Fail to find %s in idle-ip list with err %d\n",
|
|
__func__, ip_name, ip_index);
|
|
return ip_index;
|
|
}
|
|
|
|
if (ip_index > IDLE_IP_MAX_CONFIGURABLE_INDEX) {
|
|
pr_err("%s: %s index %d is out of range\n",
|
|
__func__, ip_name, ip_index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* If it successes to find IP in idle_ip list, we set
|
|
* corresponding bit in idle_ip mask.
|
|
*/
|
|
exynos_create_idle_ip_mask(ip_index);
|
|
|
|
return ip_index;
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(ip_idle_lock);
|
|
void exynos_update_ip_idle_status(int ip_index, int idle)
|
|
{
|
|
unsigned long flags;
|
|
int reg_index;
|
|
|
|
/*
|
|
* If ip_index is not valid, it should not update IDLE_IP.
|
|
*/
|
|
if (ip_index < 0 || ip_index > IDLE_IP_MAX_CONFIGURABLE_INDEX)
|
|
return;
|
|
|
|
reg_index = convert_idle_ip_index(&ip_index);
|
|
|
|
spin_lock_irqsave(&ip_idle_lock, flags);
|
|
exynos_pmu_update(PMU_IDLE_IP(reg_index),
|
|
1 << ip_index, idle << ip_index);
|
|
spin_unlock_irqrestore(&ip_idle_lock, flags);
|
|
|
|
return;
|
|
}
|
|
|
|
void exynos_get_idle_ip_list(char *(*idle_ip_list)[IDLE_IP_REG_SIZE])
|
|
{
|
|
struct device_node *np = of_find_node_by_name(NULL, "exynos-powermode");
|
|
int size;
|
|
const char *list[IDLE_IP_MAX_CONFIGURABLE_INDEX];
|
|
int i, bit, reg_index;
|
|
|
|
size = of_property_count_strings(np, "idle-ip");
|
|
if (size < 0)
|
|
return;
|
|
|
|
of_property_read_string_array(np, "idle-ip", list, size);
|
|
for (i = 0, bit = 0; i < size; i++, bit = i) {
|
|
reg_index = convert_idle_ip_index(&bit);
|
|
idle_ip_list[reg_index][bit] = (char *)list[i];
|
|
}
|
|
|
|
size = of_property_count_strings(np, "fix-idle-ip");
|
|
if (size < 0)
|
|
return;
|
|
|
|
of_property_read_string_array(np, "fix-idle-ip", list, size);
|
|
for (i = 0; i < size; i++) {
|
|
if (!of_property_read_u32_index(np, "fix-idle-ip-index", i, &bit)) {
|
|
reg_index = convert_idle_ip_index(&bit);
|
|
idle_ip_list[reg_index][bit] = (char *)list[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void __init init_idle_ip(void)
|
|
{
|
|
struct device_node *np = of_find_node_by_name(NULL, "exynos-powermode");
|
|
int mode, index, size, i;
|
|
|
|
for_each_syspwr_mode(mode)
|
|
for_each_idle_ip(index)
|
|
powermode_info->idle_ip_mask[mode][index] = 0xFFFFFFFF;
|
|
|
|
/*
|
|
* To unmask fixed idle-ip, fix-idle-ip and fix-idle-ip-index,
|
|
* both properties must be existed and size must be same.
|
|
*/
|
|
if (!of_find_property(np, "fix-idle-ip", NULL)
|
|
|| !of_find_property(np, "fix-idle-ip-index", NULL))
|
|
return;
|
|
|
|
size = of_property_count_strings(np, "fix-idle-ip");
|
|
if (size != of_property_count_u32_elems(np, "fix-idle-ip-index")) {
|
|
pr_err("Mismatch between fih-idle-ip and fix-idle-ip-index\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < size; i++) {
|
|
of_property_read_u32_index(np, "fix-idle-ip-index", i, &index);
|
|
exynos_create_idle_ip_mask(index);
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* CPU power management *
|
|
******************************************************************************/
|
|
/**
|
|
* If cpu is powered down, c2_mask in struct exynos_powermode_info is set. On
|
|
* the contrary, cpu is powered on, c2_mask is cleard. To keep coherency of
|
|
* c2_mask, use the spinlock, c2_lock. In Exynos, it supports C2 subordinate
|
|
* power mode, CPD.
|
|
*
|
|
* - CPD (Cluster Power Down)
|
|
* All cpus in a cluster are set c2_mask, and these cpus have enough idle
|
|
* time which is longer than cpd_residency, cluster can be powered off.
|
|
*
|
|
* SICD (System Idle Clock Down) : All cpus are set c2_mask and these cpus
|
|
* have enough idle time which is longer than sicd_residency, and besides no
|
|
* device is operated, AP can be put into SICD.
|
|
*/
|
|
|
|
static DEFINE_SPINLOCK(c2_lock);
|
|
|
|
static void update_c2_state(bool down, unsigned int cpu)
|
|
{
|
|
if (down)
|
|
cpumask_set_cpu(cpu, &powermode_info->c2_mask);
|
|
else
|
|
cpumask_clear_cpu(cpu, &powermode_info->c2_mask);
|
|
}
|
|
|
|
static s64 get_next_event_time_us(unsigned int cpu)
|
|
{
|
|
return ktime_to_us(tick_nohz_get_sleep_length());
|
|
}
|
|
|
|
static int is_cpus_busy(unsigned int target_residency,
|
|
const struct cpumask *mask)
|
|
{
|
|
int cpu;
|
|
|
|
/*
|
|
* If there is even one cpu in "mask" which has the smaller idle time
|
|
* than "target_residency", it returns -EBUSY.
|
|
*/
|
|
for_each_cpu_and(cpu, cpu_online_mask, mask) {
|
|
if (!cpumask_test_cpu(cpu, &powermode_info->c2_mask))
|
|
return -EBUSY;
|
|
|
|
/*
|
|
* Compare cpu's next event time and target_residency.
|
|
* Next event time means idle time.
|
|
*/
|
|
if (get_next_event_time_us(cpu) < target_residency)
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_cpu_boot_cluster(unsigned int cpu)
|
|
{
|
|
/*
|
|
* The cluster included cpu0 is boot cluster
|
|
*/
|
|
return (get_cluster_id(0) == get_cluster_id(cpu));
|
|
}
|
|
|
|
static int is_cpd_available(unsigned int cpu)
|
|
{
|
|
struct cpumask mask;
|
|
|
|
if (!powermode_info->cpd_enabled)
|
|
return false;
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS7885
|
|
if (!powermode_info->cpd_enabled[get_cluster_id(cpu)])
|
|
return false;
|
|
#endif
|
|
/* If other driver blocks cpd, cpd_blocking is true */
|
|
if (powermode_info->cpd_blocking)
|
|
return false;
|
|
|
|
/*
|
|
* Power down of boot cluster have nothing to gain power consumption,
|
|
* so it is not supported.
|
|
*/
|
|
if (is_cpu_boot_cluster(cpu))
|
|
return false;
|
|
|
|
cpumask_and(&mask, topology_idle_cpumask(cpu), cpu_online_mask);
|
|
if (is_cpus_busy(powermode_info->cpd_residency, &mask))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(CONFIG_SEC_PM)
|
|
static bool jig_is_attached;
|
|
static inline bool is_jig_attached(void) { return jig_is_attached; }
|
|
#else
|
|
static inline bool is_jig_attached(void) { return false; }
|
|
#endif /* CONFIG_SEC_PM */
|
|
|
|
static int is_sicd_available(unsigned int cpu)
|
|
{
|
|
if (!powermode_info->sicd_enabled)
|
|
return false;
|
|
|
|
if (is_jig_attached())
|
|
return false;
|
|
|
|
/*
|
|
* When the cpu in non-boot cluster enters SICD, interrupts of
|
|
* boot cluster is not blocked. For stability, SICD entry by
|
|
* non-boot cluster is not supported.
|
|
*/
|
|
if (!is_cpu_boot_cluster(cpu)) {
|
|
/* If cpu4~7 are last core in the them CL, AP can't go on
|
|
* SICD mode. Only, cores in booting CL can enter SICD mode.
|
|
* This code applies to enter SICD mode depending on cpu3
|
|
* through an IPI from non booting CL for 3 clusters SOC only.
|
|
*/
|
|
#ifdef CONFIG_SOC_EXYNOS7885
|
|
arch_send_wakeup_ipi_mask(cpumask_of(3));
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
if (is_cpus_busy(powermode_info->sicd_residency, cpu_online_mask))
|
|
return false;
|
|
|
|
if (syspwr_mode_available(SYS_SICD))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* cluster_idle_state shows whether cluster is in idle or not.
|
|
*
|
|
* check_cluster_idle_state() : Show cluster idle state.
|
|
* If it returns true, cluster is in idle state.
|
|
* update_cluster_idle_state() : Update cluster idle state.
|
|
*/
|
|
static int cluster_idle_state[CONFIG_NR_CLUSTERS];
|
|
|
|
static int check_cluster_idle_state(unsigned int cpu)
|
|
{
|
|
return cluster_idle_state[get_cluster_id(cpu)];
|
|
}
|
|
|
|
static void update_cluster_idle_state(int idle, unsigned int cpu)
|
|
{
|
|
cluster_idle_state[get_cluster_id(cpu)] = idle;
|
|
}
|
|
|
|
/**
|
|
* Exynos cpuidle driver call exynos_cpu_pm_enter() and exynos_cpu_pm_exit() to
|
|
* handle platform specific configuration to power off the cpu power domain.
|
|
*/
|
|
int exynos_cpu_pm_enter(unsigned int cpu, int index)
|
|
{
|
|
cpu_disable(cpu);
|
|
|
|
spin_lock(&c2_lock);
|
|
update_c2_state(true, cpu);
|
|
|
|
/*
|
|
* Below sequence determines whether to power down the cluster/enter SICD
|
|
* or not. If idle time of cpu is not enough, go out of this function.
|
|
*/
|
|
if (get_next_event_time_us(cpu) <
|
|
min(powermode_info->cpd_residency, powermode_info->sicd_residency))
|
|
goto out;
|
|
|
|
if (is_cpd_available(cpu)) {
|
|
cluster_disable(cpu);
|
|
update_cluster_idle_state(true, cpu);
|
|
|
|
index = PSCI_CLUSTER_SLEEP;
|
|
}
|
|
|
|
if (is_sicd_available(cpu)) {
|
|
if (exynos_prepare_sys_powerdown(SYS_SICD))
|
|
goto out;
|
|
|
|
s3c24xx_serial_fifo_wait();
|
|
exynos_ss_cpuidle(get_sys_powerdown_str(SYS_SICD),
|
|
0, 0, ESS_FLAG_IN);
|
|
|
|
powermode_info->sicd_entered = true;
|
|
index = PSCI_SYSTEM_IDLE;
|
|
}
|
|
out:
|
|
spin_unlock(&c2_lock);
|
|
|
|
return index;
|
|
}
|
|
|
|
void exynos_cpu_pm_exit(unsigned int cpu, int enter_failed)
|
|
{
|
|
if (enter_failed)
|
|
cpu_enable(cpu);
|
|
|
|
spin_lock(&c2_lock);
|
|
|
|
if (check_cluster_idle_state(cpu)) {
|
|
cluster_enable(cpu);
|
|
update_cluster_idle_state(false, cpu);
|
|
}
|
|
|
|
if (powermode_info->sicd_entered) {
|
|
exynos_wakeup_sys_powerdown(SYS_SICD, enter_failed);
|
|
exynos_ss_cpuidle(get_sys_powerdown_str(SYS_SICD),
|
|
0, 0, ESS_FLAG_OUT);
|
|
|
|
powermode_info->sicd_entered = false;
|
|
}
|
|
|
|
update_c2_state(false, cpu);
|
|
|
|
spin_unlock(&c2_lock);
|
|
}
|
|
|
|
/**
|
|
* powermode_attr_read() / show_##file_name() -
|
|
* print out power mode information
|
|
*
|
|
* powermode_attr_write() / store_##file_name() -
|
|
* sysfs write access
|
|
*/
|
|
#define show_one(file_name, object) \
|
|
static ssize_t show_##file_name(struct kobject *kobj, \
|
|
struct kobj_attribute *attr, char *buf) \
|
|
{ \
|
|
return snprintf(buf, 3, "%d\n", \
|
|
powermode_info->object); \
|
|
}
|
|
|
|
#define store_one(file_name, object) \
|
|
static ssize_t store_##file_name(struct kobject *kobj, \
|
|
struct kobj_attribute *attr, const char *buf, \
|
|
size_t count) \
|
|
{ \
|
|
int input; \
|
|
\
|
|
if (!sscanf(buf, "%1d", &input)) \
|
|
return -EINVAL; \
|
|
\
|
|
powermode_info->object = !!input; \
|
|
\
|
|
return count; \
|
|
}
|
|
|
|
#define attr_rw(_name) \
|
|
static struct kobj_attribute _name = \
|
|
__ATTR(_name, 0644, show_##_name, store_##_name)
|
|
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
show_one(cpd, cpd_enabled);
|
|
store_one(cpd, cpd_enabled);
|
|
|
|
attr_rw(cpd);
|
|
#else
|
|
static ssize_t store_cpd_enabled(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
unsigned int cpd[CONFIG_NR_CLUSTERS];
|
|
int i = 0;
|
|
int ret = 0;
|
|
|
|
for (i = 0; i < CONFIG_NR_CLUSTERS; i++) {
|
|
ret += sscanf(buf, "%1u", &cpd[i]);
|
|
}
|
|
|
|
if (ret != CONFIG_NR_CLUSTERS)
|
|
return -EINVAL;
|
|
for (i = 0; i < powermode_info->num_cpd_enabled; i++)
|
|
powermode_info->cpd_enabled[i] = !!(cpd[i]);
|
|
|
|
return count;
|
|
}
|
|
static ssize_t show_cpd_enabled(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
ssize_t count = 0;
|
|
int i = 0;
|
|
|
|
for (i = 0; i < powermode_info->num_cpd_enabled; i++) {
|
|
unsigned int isCpd = powermode_info->cpd_enabled[i];
|
|
|
|
count += snprintf(&buf[count], 3, "%1u ", isCpd);
|
|
}
|
|
count += snprintf(&buf[count - 1], 2, "\n");
|
|
|
|
return count;
|
|
}
|
|
static struct kobj_attribute cpd =
|
|
__ATTR(cpd, 0644, show_cpd_enabled, store_cpd_enabled);
|
|
#endif
|
|
show_one(sicd, sicd_enabled);
|
|
store_one(sicd, sicd_enabled);
|
|
|
|
attr_rw(sicd);
|
|
|
|
#endif /* __CONFIG_ARM64_EXYNOS_CPUIDLE__ */
|
|
|
|
/******************************************************************************
|
|
* System power mode *
|
|
******************************************************************************/
|
|
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(powermode_info->eint_wakeup_mask, (u32)eintmask);
|
|
|
|
for (i = 0; i < powermode_info->num_wakeup_mask; i++)
|
|
exynos_pmu_write(powermode_info->wakeup_mask_offset[i],
|
|
powermode_info->wakeup_mask[mode][i]);
|
|
}
|
|
|
|
int exynos_prepare_sys_powerdown(enum sys_powerdown mode)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef CONFIG_ARM64_EXYNOS_CPUIDLE
|
|
exynos_set_idle_ip_mask(mode);
|
|
#endif
|
|
exynos_set_wakeupmask(mode);
|
|
|
|
ret = cal_pm_enter(mode);
|
|
if (ret) {
|
|
pr_err("CAL Fail to set powermode\n");
|
|
goto out;
|
|
}
|
|
|
|
switch (mode) {
|
|
case SYS_SICD:
|
|
exynos_pm_notify(SICD_ENTER);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
out:
|
|
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);
|
|
|
|
switch (mode) {
|
|
case SYS_SICD:
|
|
exynos_pm_notify(SICD_EXIT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Notifier *
|
|
******************************************************************************/
|
|
static int exynos_cpuidle_hotcpu_callback(struct notifier_block *nfb,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
unsigned int cpu = (unsigned long)hcpu;
|
|
struct cpumask mask;
|
|
|
|
/*
|
|
* CPU_STARTING and CPU_DYING event are executed by incomming or
|
|
* outgoing cpu itself.
|
|
*/
|
|
switch (action) {
|
|
case CPU_STARTING:
|
|
case CPU_STARTING_FROZEN:
|
|
cpumask_and(&mask, topology_idle_cpumask(cpu), cpu_online_mask);
|
|
if (cpumask_weight(&mask) == 0)
|
|
cluster_enable(cpu);
|
|
|
|
cpu_enable(cpu);
|
|
break;
|
|
case CPU_DYING:
|
|
case CPU_DYING_FROZEN:
|
|
cpu_disable(cpu);
|
|
|
|
cpumask_and(&mask, topology_idle_cpumask(cpu), cpu_online_mask);
|
|
if (cpumask_weight(&mask) == 0)
|
|
cluster_disable(cpu);
|
|
break;
|
|
case CPU_DEAD:
|
|
case CPU_DEAD_FROZEN:
|
|
cpumask_and(&mask, topology_idle_cpumask(cpu), cpu_online_mask);
|
|
if (cpumask_weight(&mask) == 0) {
|
|
/* Wait power down cluster */
|
|
while (exynos_cpu.cluster_state(cpu))
|
|
udelay(1);
|
|
}
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block __refdata cpuidle_hotcpu_notifier = {
|
|
.notifier_call = exynos_cpuidle_hotcpu_callback,
|
|
.priority = INT_MAX,
|
|
};
|
|
|
|
/**
|
|
* powermode_cpufreq_transition() blocks to power down the cluster
|
|
* before frequency changing. And it release the blocking after
|
|
* completion of frequency changing.
|
|
*/
|
|
#ifdef CONFIG_ARM64_EXYNOS_CPUIDLE
|
|
static void nop_func(void *info) {}
|
|
#endif
|
|
static int exynos_powermode_cpufreq_transition(struct notifier_block *nb,
|
|
unsigned long val, void *data)
|
|
{
|
|
#ifdef CONFIG_ARM64_EXYNOS_CPUIDLE
|
|
struct cpufreq_freqs *freq = data;
|
|
int cpu = freq->cpu;
|
|
|
|
/*
|
|
* Boot cluster does not support cluster power down.
|
|
* Do nothing in this notify call.
|
|
*/
|
|
if (is_cpu_boot_cluster(cpu))
|
|
return NOTIFY_OK;
|
|
|
|
if (!powermode_info->cpd_enabled)
|
|
return NOTIFY_OK;
|
|
|
|
#ifdef CONFIG_SOC_EXYNOS7885
|
|
if (!powermode_info->cpd_enabled[get_cluster_id(cpu)])
|
|
return NOTIFY_OK;
|
|
#endif
|
|
switch (val) {
|
|
case CPUFREQ_PRECHANGE:
|
|
powermode_info->cpd_blocking = true;
|
|
if (check_cluster_idle_state(cpu))
|
|
smp_call_function_single(cpu, nop_func, NULL, 0);
|
|
break;
|
|
case CPUFREQ_POSTCHANGE:
|
|
powermode_info->cpd_blocking = false;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block exynos_powermode_cpufreq_notifier = {
|
|
.notifier_call = exynos_powermode_cpufreq_transition,
|
|
};
|
|
|
|
/******************************************************************************
|
|
* Extern function *
|
|
******************************************************************************/
|
|
int exynos_rtc_wakeup(void)
|
|
{
|
|
#define WAKEUP_MASK_RTC_TICK BIT(2)
|
|
#define WAKEUP_MASK_RTC_ALARM BIT(1)
|
|
unsigned int sleep_mask = powermode_info->wakeup_mask[SYS_SLEEP][0];
|
|
|
|
if (!(sleep_mask & WAKEUP_MASK_RTC_ALARM) ||
|
|
!(sleep_mask & WAKEUP_MASK_RTC_TICK))
|
|
return 0;
|
|
|
|
return -ENXIO;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Driver initialization *
|
|
******************************************************************************/
|
|
static int alloc_wakeup_mask(int num_wakeup_mask)
|
|
{
|
|
unsigned int mode;
|
|
|
|
powermode_info->wakeup_mask_offset = kzalloc(sizeof(unsigned int)
|
|
* num_wakeup_mask, GFP_KERNEL);
|
|
if (!powermode_info->wakeup_mask_offset)
|
|
return -ENOMEM;
|
|
|
|
for_each_syspwr_mode(mode) {
|
|
powermode_info->wakeup_mask[mode] = kzalloc(sizeof(unsigned int)
|
|
* num_wakeup_mask, GFP_KERNEL);
|
|
|
|
if (!powermode_info->wakeup_mask[mode])
|
|
goto free_reg_offset;
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_reg_offset:
|
|
for_each_syspwr_mode(mode)
|
|
if (powermode_info->wakeup_mask[mode])
|
|
kfree(powermode_info->wakeup_mask[mode]);
|
|
|
|
kfree(powermode_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");
|
|
powermode_info->num_wakeup_mask = of_get_child_count(root);
|
|
|
|
ret = alloc_wakeup_mask(powermode_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, &powermode_info->wakeup_mask[mode][mask_index]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32(child, "reg-offset",
|
|
&powermode_info->wakeup_mask_offset[mask_index]);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mask_index++;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "eint-wakeup-mask", &powermode_info->eint_wakeup_mask))
|
|
pr_warn("No matching property: eint_wakeup_mask\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init dt_init(void)
|
|
{
|
|
struct device_node *np = of_find_node_by_name(NULL, "exynos-powermode");
|
|
int ret;
|
|
|
|
ret = parsing_dt_wakeup_mask(np);
|
|
if (ret)
|
|
pr_warn("Fail to initialize the wakeup mask with err = %d\n", ret);
|
|
|
|
if (of_property_read_u32(np, "cpd_residency", &powermode_info->cpd_residency))
|
|
pr_warn("No matching property: cpd_residency\n");
|
|
|
|
if (of_property_read_u32(np, "sicd_residency", &powermode_info->sicd_residency))
|
|
pr_warn("No matching property: sicd_residency\n");
|
|
#ifndef CONFIG_SOC_EXYNOS7885
|
|
if (of_property_read_u32(np, "cpd_enabled", &powermode_info->cpd_enabled))
|
|
pr_warn("No matching property: cpd_enabled\n");
|
|
#else
|
|
ret = of_property_count_u32_elems(np, "cpd_enabled");
|
|
if (!ret) {
|
|
pr_warn("No matching property: cpd_enabled\n");
|
|
} else {
|
|
powermode_info->num_cpd_enabled = ret;
|
|
powermode_info->cpd_enabled = kcalloc(ret, sizeof(unsigned int), GFP_KERNEL);
|
|
of_property_read_u32_array(np, "cpd_enabled", powermode_info->cpd_enabled, ret);
|
|
}
|
|
#endif
|
|
if (of_property_read_u32(np, "sicd_enabled", &powermode_info->sicd_enabled))
|
|
pr_warn("No matching property: sicd_enabled\n");
|
|
|
|
return 0;
|
|
}
|
|
late_initcall_sync(dt_init);
|
|
|
|
static int __init exynos_powermode_early_init(void)
|
|
{
|
|
return register_hotcpu_notifier(&cpuidle_hotcpu_notifier);
|
|
}
|
|
early_initcall(exynos_powermode_early_init);
|
|
|
|
static int __init exynos_powermode_init(void)
|
|
{
|
|
powermode_info = kzalloc(sizeof(struct exynos_powermode_info), GFP_KERNEL);
|
|
if (powermode_info == NULL) {
|
|
pr_err("%s: failed to allocate exynos_powermode_info\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef CONFIG_ARM64_EXYNOS_CPUIDLE
|
|
init_idle_ip();
|
|
|
|
if (sysfs_create_file(power_kobj, &cpd.attr))
|
|
pr_err("%s: failed to create sysfs to control CPD\n", __func__);
|
|
|
|
if (sysfs_create_file(power_kobj, &sicd.attr))
|
|
pr_err("%s: failed to create sysfs to control SICD\n", __func__);
|
|
#endif
|
|
|
|
cpufreq_register_notifier(&exynos_powermode_cpufreq_notifier,
|
|
CPUFREQ_TRANSITION_NOTIFIER);
|
|
|
|
return 0;
|
|
}
|
|
arch_initcall(exynos_powermode_init);
|
|
|
|
#if defined(CONFIG_SEC_PM) && defined(CONFIG_MUIC_NOTIFIER)
|
|
struct notifier_block cpuidle_muic_nb;
|
|
|
|
static int exynos_cpuidle_muic_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
#ifdef CONFIG_CCIC_NOTIFIER
|
|
CC_NOTI_ATTACH_TYPEDEF *pnoti = (CC_NOTI_ATTACH_TYPEDEF *)data;
|
|
muic_attached_dev_t attached_dev = pnoti->cable_type;
|
|
#else
|
|
muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data;
|
|
#endif
|
|
|
|
switch (attached_dev) {
|
|
case ATTACHED_DEV_JIG_UART_OFF_MUIC:
|
|
case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC:
|
|
case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC:
|
|
case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC:
|
|
case ATTACHED_DEV_JIG_UART_ON_MUIC:
|
|
case ATTACHED_DEV_JIG_UART_ON_VB_MUIC:
|
|
if (action == MUIC_NOTIFY_CMD_DETACH)
|
|
jig_is_attached = false;
|
|
else if (action == MUIC_NOTIFY_CMD_ATTACH)
|
|
jig_is_attached = true;
|
|
else
|
|
pr_err("%s: ACTION Error!\n", __func__);
|
|
|
|
pr_info("%s: JIG(%d) is %s\n", __func__, attached_dev,
|
|
jig_is_attached ? "attached" : "detached");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static int __init exynos_powermode_muic_notifier_init(void)
|
|
{
|
|
return muic_notifier_register(&cpuidle_muic_nb,
|
|
exynos_cpuidle_muic_notifier, MUIC_NOTIFY_DEV_CPUIDLE);
|
|
}
|
|
late_initcall(exynos_powermode_muic_notifier_init);
|
|
#endif /* CONFIG_SEC_PM && CONFIG_MUIC_NOTIFIER */
|