drivers: soc: qcom: Snapshot of idle/sleep driver as of msm-4.4

This is a snapshot of the Sleep driver and realted functionality as of
'commit b67bb93f565e ("power: reset: Do not disable SDI during reset")'
on msm-4.4 branch.

Moved Energy Awareness driver and Idle stats driver from
drivers/power/qcom
to drivers/soc/qcom.

Remove lpm-workarounds file that no longer is applicable on newer
branches.

Updated the patch to fix some checkpatch issues
	- Remove BUG, conver to WARN
	- White space issues
	- Replace S_IRUGO etc
	- Remove return on void functions
	- Add function arguments
	- unsigned -> unsigned int
	- Add comments around wmb, mb.
	- Fix comment styles
	- kzalloc to kcalloc
	- Updated MSM_PM Kconfig help

Change-Id: I98fec26849898c5c66abbb1b094439780c23964d
Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
This commit is contained in:
Mahesh Sivasubramanian 2016-02-01 10:40:26 -07:00
commit c2ea76f6d3
20 changed files with 7292 additions and 0 deletions

View file

@ -0,0 +1,324 @@
* Low Power Management Levels
The application processor in MSM can do a variety of C-States for low power
management. The LPM module performs the System low power modes based on
the latency/residency information of the individual CPUs and clusters.
LPM-levels defines a hierarchy of low power modes that a cluster and
clusters/cpus within that cluster can enter. The bottom hierarchy level
represents the low power modes that a CPU can enter. The CPU low power nodes
are associated with a cluster that defines the low power modes that a cluster
can enter. For system involving a hierarchy of clusters, the cluster low power
modes can be contained within another cluster.
[Top Level Node]
Required properties:
- compatible: "qcom,lpm-levels"
[Node bindings for qcom,pm-cluster]
Required properties:
- reg - The numeric cluster id
- label: Identifies the cluster name. The name will be
used when reporting the stats for each low power mode.
- qcom,spm-device-names: List of SPM device names which control the
low power modes for this driver. The lpm driver uses the device name
to obtain a handle to the SPM driver that controls the cluster's low
power mode. This is only required if "qcom,use-psci" is not defined.
- qcom,default-level: The default low power level that a cluster is
programmed. The SPM of the corresponding device is configured at this
low power mode by default.
- qcom,cpu: List of CPU phandles to identify the CPUs associated with
this cluster. This property is required if and only if the cluster
node contains a qcom,pm-cpu node.
qcom,pm-cluster contains qcom,pm-cluster-level nodes which identify
the various low power modes that the cluster can enter. The
qcom,pm-cluster node should also include another cluster node or a cpu
node that defines their respective low power modes.
[Node bindings for qcom,pm-cluster-level]
Required properties:
- reg: The numeric cluster level id
- label: Name to identify the low power mode in stats
module.
- qcom,spm-<device-name>-mode: For each SPM device defined in
qcom,spm-devices-names, a corresponding entry identifying the low
power mode is expected. For example, the qcom,pm-cluster node contains
a SPM device by name "l2" then the cluster level should contain a
qcom,spm-l2-mode. When a cluster level is chosen ,the SPM device is
programmed with its
corresponding low power mode. The accepted values for this property
are:
- "active"
- "wfi"
- "retention"
- "gdhs"
- "pc"
- "fpc"
- qcom,min-child-idx: The minimum level that a child CPU should be in
before this level can be chosen. This property is required for all
non-default level.
- qcom,latency-us: The latency in handling the interrupt if this level
was chosen, in uSec
- qcom,ss-power: The steady state power expelled when the processor is
in this level in mWatts
- qcom,energy-overhead: The energy used up in entering and exiting
this level in mWatts.uSec
- qcom,time-overhead: The time spent in entering and exiting this
level in uS
Optional properties:
- qcom,notify-rpm: When set, the driver flushes the RPM sleep set and
configures the virtual MPM driver in prepration for a RPM assisted
sleep.
- qcom,last-level - When set, the cluster level is applied only when
there is 1 online core.
- qcom,disable-dynamic-int-routing: When set disables the dynamic
routing of rpm-smd and mpm interrupts to next wake up core.
- qcom,use-psci: This boolean property allows the LPM modules to
terminate in PSCI to configure SPM for low power modes.
- qcom,psci-mode-shift: The property is used to determine with bit
location of the cluster mode in the composite state ID used to define
cluster low power modes in PSCI v1.0. Required only if qcom,use-psci
is defined at the lpm-levels root node.
- qcom,psci-mode-mask: The property is used to determine with bit
mask of the cluster mode in the composite state ID used to define
cluster low power modes in PSCI v1.0. Required only if qcom,use-psci
is defined at the lpm-levels root node.
- qcom,psci-mode: ID to be passed into the PSCI firmware. Required
only if qcom,use-psci is defined at the lpm-levels root node.
- qcom,is-reset: This boolean property will tell whether
cluster level need power management notifications to be sent out
or not for the drivers to prepare for cluster collapse.
- qcom,hyp-psci: This property is used to determine if the cpu
enters the low power mode within hypervisor.
- qcom,reset-level: This property is used to determine in this
low power mode only control logic power collapse happens or memory
logic power collapse aswell happens or retention state.
The accepted values for this property are:
"LPM_RESET_LVL_NONE" - No power collapse
"LPM_RESET_LVL_RET" - Retention state
"LPM_RESET_LVL_GDHS" - Only control logic power collapse (GDHS)
"LPM_RESET_LVL_PC" - Control logic and memory logic
power collapse (PC)
[Node bindings for qcom,pm-cpu]
qcom,pm-cpu contains the low power modes that a cpu could enter. Currently it
doesn't have any required properties and is a container for
qcom,pm-cpu-levels.
[Node bindings for qcom,pm-cpu-levels]
Required properties:
- reg: The numeric cpu level id
- qcom,spm-cpu-mode: The sleep mode of the processor, values for the
property are:
"wfi" - Wait for Interrupt
"retention" - Retention
"standalone_pc" - Standalone power collapse
"pc" - Power Collapse
- qcom,latency-us: The latency in handling the interrupt if this level
was chosen, in uSec
- qcom,ss-power: The steady state power expelled when the processor is
in this level in mWatts
- qcom,energy-overhead: The energy used up in entering and exiting
this level in mWatts.uSec
- qcom,time-overhead: The time spent in entering and exiting this
level in uS
- qcom,use-broadcast-timer: Indicates that the timer gets reset during
power collapse and the cpu relies on Broadcast timer for scheduled
wakeups. Required only for states where the CPUs internal timer state
is lost.
Optional properties:
- qcom,psci-mode-shift: Same as cluster level fields.
- qcom,psci-mode-mask: Same as cluster level fields.
- qcom,psci-cpu-mode: ID to be passed into PSCI firmware.
- qcom,jtag-save-restore: A boolean specifying jtag registers save and restore
required are not.
- qcom,is-reset: This boolean property maps to "power state" bit in PSCI
state_id configuration. This property will tell whether CPU get reset for
a particular LPM or not. This property will also be used to notify the
drivers in case of cpu reset.
[Example dts]
qcom,lpm-levels {
#address-cells = <1>;
#size-cells = <0>;
compatible = "qcom,lpm-levels";
qcom,pm-cluster@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
label = "system";
qcom,spm-device-names = "cci";
qcom,default-level = <0>;
qcom,pm-cluster-level@0{
reg = <0>;
label = "system-cci-retention";
qcom,spm-cci-mode = "retention";
qcom,latency-us = <100>;
qcom,ss-power = <1000>;
qcom,energy-overhead = <300000>;
qcom,time-overhead = <100>;
};
qcom,pm-cluster-level@2{
reg = <1>;
label = "system-cci-pc";
qcom,spm-cci-mode = "pc";
qcom,latency-us = <30000>;
qcom,ss-power = <83>;
qcom,energy-overhead = <2274420>;
qcom,time-overhead = <6605>;
qcom,min-child-idx = <1>;
qcom,notify-rpm;
};
qcom,pm-cluster@0{
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
label = "a53";
qcom,spm-device-names = "l2";
qcom,default-level=<0>;
qcom,cpu = <&CPU0 &CPU1 &CPU2 &CPU3>;
qcom,pm-cluster-level@0{
reg = <0>;
label = "a53-l2-retention";
qcom,spm-l2-mode = "retention";
qcom,latency-us = <100>;
qcom,ss-power = <1000>;
qcom,energy-overhead = <300000>;
qcom,time-overhead = <100>;
};
qcom,pm-cluster-level@1{
reg = <1>;
label = "a53-l2-pc";
qcom,spm-l2-mode = "pc";
qcom,latency-us = <30000>;
qcom,ss-power = <83>;
qcom,energy-overhead = <2274420>;
qcom,time-overhead = <6605>;
qcom,min-child-idx = <3>;
};
qcom,pm-cpu {
#address-cells = <1>;
#size-cells = <0>;
qcom,pm-cpu-level@0 {
reg = <0>;
qcom,spm-cpu-mode = "wfi";
qcom,latency-us = <1>;
qcom,ss-power = <715>;
qcom,energy-overhead = <17700>;
qcom,time-overhead = <2>;
};
qcom,pm-cpu-level@1 {
reg = <1>;
qcom,spm-cpu-mode = "retention";
qcom,latency-us = <35>;
qcom,ss-power = <542>;
qcom,energy-overhead = <34920>;
qcom,time-overhead = <40>;
};
qcom,pm-cpu-level@2 {
reg = <2>;
qcom,spm-cpu-mode = "standalone_pc";
qcom,latency-us = <300>;
qcom,ss-power = <476>;
qcom,energy-overhead = <225300>;
qcom,time-overhead = <350>;
};
qcom,pm-cpu-level@3 {
reg = <3>;
qcom,spm-cpu-mode = "pc";
qcom,latency-us = <500>;
qcom,ss-power = <163>;
qcom,energy-overhead = <577736>;
qcom,time-overhead = <1000>;
};
};
};
qcom,pm-cluster@1{
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
label = "a57";
qcom,spm-device-names = "l2";
qcom,default-level=<0>;
qcom,cpu = <&CPU4 &CPU5 &CPU6 &CPU7>;
qcom,pm-cluster-level@0{
reg = <0>;
label = "a57-l2-retention";
qcom,spm-l2-mode = "retention";
qcom,latency-us = <100>;
qcom,ss-power = <1000>;
qcom,energy-overhead = <300000>;
qcom,time-overhead = <100>;
};
qcom,pm-cluster-level@2{
reg = <1>;
label = "a57-l2-pc";
qcom,spm-l2-mode = "pc";
qcom,latency-us = <30000>;
qcom,ss-power = <83>;
qcom,energy-overhead = <2274420>;
qcom,time-overhead = <6605>;
qcom,min-child-idx = <3>;
};
qcom,pm-cpu {
#address-cells = <1>;
#size-cells = <0>;
qcom,pm-cpu-level@0 {
reg = <0>;
qcom,spm-cpu-mode = "wfi";
qcom,latency-us = <1>;
qcom,ss-power = <715>;
qcom,energy-overhead = <17700>;
qcom,time-overhead = <2>;
};
qcom,pm-cpu-level@1 {
reg = <1>;
qcom,spm-cpu-mode = "retention";
qcom,latency-us = <35>;
qcom,ss-power = <542>;
qcom,energy-overhead = <34920>;
qcom,time-overhead = <40>;
};
qcom,pm-cpu-level@2 {
reg = <2>;
qcom,spm-cpu-mode = "standalone_pc";
qcom,latency-us = <300>;
qcom,ss-power = <476>;
qcom,energy-overhead = <225300>;
qcom,time-overhead = <350>;
};
qcom,pm-cpu-level@3 {
reg = <3>;
qcom,spm-cpu-mode = "pc";
qcom,latency-us = <500>;
qcom,ss-power = <163>;
qcom,energy-overhead = <577736>;
qcom,time-overhead = <1000>;
};
};
};
};
};

View file

@ -0,0 +1,71 @@
MSM Core Energy Aware driver
The Energy Aware driver provides per core power and temperature
information to the scheduler for it to make more power efficient
scheduling decision.
The required properties for the Energy-aware driver are:
- compatible: "qcom,apss-core-ea"
- reg: Physical address mapped to this device
Required nodes:
- ea@X: Parent node that has the sensor mapping for each cpu.
This node's phandle is provided within cpu node
to invoke/probe energy-aware only for available cpus.
There should be one such node present for each cpu.
Optional properties:
- qcom,low-hyst-temp: Degrees C below which the power numbers
need to be recomputed for the cores and reset
the threshold. If this is not present, the default
value is 10C.
- qcom,high-hyst-temp: Degrees C above which the power numbers
need to be recomputed for the cores and reset
the threshold. If this property is not present,
the default value is 5C.
- qcom,polling-interval: Interval for which the power numbers
need to be recomputed for the cores if there
is no change in threshold. If this property is not
present, the power is recalculated only on
temperature threshold notifications.
-qcom,throttling-temp: Temperature threshold for cpu frequency mitigation.
The value should be set same as the threshold temperature
in thermal module - 5 C, such that there is a bandwidth to
control the cores before frequency mitigation happens.
[Second level nodes]
Require properties to define per core characteristics:
- sensor: Sensor phandle to map a particular sensor to the core.
If this property is not present, then the core is assumed
to be at 40C for all the power estimations. No sensor
threshold is set. This phandle's compatible property is
"qcom,sensor-information". This driver relies on the
sensor-type and scaling-factor information provided in this
phandle.
Example
qcom,msm-core@0xfc4b0000 {
compatible = "qcom,apss-core-ea";
reg = <0xfc4b0000 0x1000>;
qcom,low-hyst-temp = <10>;
qcom,high-hyst-temp = <5>;
qcom,polling-interval = <50>;
ea0: ea0 {
sensor = <&sensor_information0>;
};
ea1: ea1 {
sensor = <&sensor_information1>;
};
};
CPU0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x0>;
qcom,ea = <&ea0>;
};

View file

@ -27,3 +27,4 @@ obj-$(CONFIG_MIPS_CPS_CPUIDLE) += cpuidle-cps.o
# POWERPC drivers
obj-$(CONFIG_PSERIES_CPUIDLE) += cpuidle-pseries.o
obj-$(CONFIG_POWERNV_CPUIDLE) += cpuidle-powernv.o
obj-$(CONFIG_MSM_PM) += lpm-levels.o lpm-levels-of.o lpm-workarounds.o

File diff suppressed because it is too large Load diff

1967
drivers/cpuidle/lpm-levels.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,166 @@
/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <soc/qcom/pm.h>
#include <soc/qcom/spm.h>
#define NR_LPM_LEVELS 8
#define MAXSAMPLES 5
#define CLUST_SMPL_INVLD_TIME 40000
extern bool use_psci;
struct lpm_lookup_table {
uint32_t modes;
const char *mode_name;
};
struct power_params {
uint32_t latency_us; /* Enter + Exit latency */
uint32_t ss_power; /* Steady state power */
uint32_t energy_overhead; /* Enter + exit over head */
uint32_t time_overhead_us; /* Enter + exit overhead */
uint32_t residencies[NR_LPM_LEVELS];
uint32_t min_residency;
uint32_t max_residency;
};
struct lpm_cpu_level {
const char *name;
enum msm_pm_sleep_mode mode;
bool use_bc_timer;
struct power_params pwr;
unsigned int psci_id;
bool is_reset;
bool jtag_save_restore;
bool hyp_psci;
int reset_level;
};
struct lpm_cpu {
struct lpm_cpu_level levels[NR_LPM_LEVELS];
int nlevels;
unsigned int psci_mode_shift;
unsigned int psci_mode_mask;
struct lpm_cluster *parent;
};
struct lpm_level_avail {
bool idle_enabled;
bool suspend_enabled;
struct kobject *kobj;
struct kobj_attribute idle_enabled_attr;
struct kobj_attribute suspend_enabled_attr;
void *data;
int idx;
bool cpu_node;
};
struct lpm_cluster_level {
const char *level_name;
int *mode; /* SPM mode to enter */
int min_child_level;
struct cpumask num_cpu_votes;
struct power_params pwr;
bool notify_rpm;
bool disable_dynamic_routing;
bool sync_level;
bool last_core_only;
struct lpm_level_avail available;
unsigned int psci_id;
bool is_reset;
int reset_level;
};
struct low_power_ops {
struct msm_spm_device *spm;
int (*set_mode)(struct low_power_ops *ops, int mode, bool notify_rpm);
enum msm_pm_l2_scm_flag tz_flag;
};
struct cluster_history {
uint32_t resi[MAXSAMPLES];
int mode[MAXSAMPLES];
int64_t stime[MAXSAMPLES];
uint32_t hptr;
uint32_t hinvalid;
uint32_t htmr_wkup;
uint64_t entry_time;
int entry_idx;
int nsamp;
int flag;
};
struct lpm_cluster {
struct list_head list;
struct list_head child;
const char *cluster_name;
const char **name;
unsigned long aff_level; /* Affinity level of the node */
struct low_power_ops *lpm_dev;
int ndevices;
struct lpm_cluster_level levels[NR_LPM_LEVELS];
int nlevels;
enum msm_pm_l2_scm_flag l2_flag;
int min_child_level;
int default_level;
int last_level;
struct lpm_cpu *cpu;
struct cpuidle_driver *drv;
spinlock_t sync_lock;
struct cpumask child_cpus;
struct cpumask num_children_in_sync;
struct lpm_cluster *parent;
struct lpm_stats *stats;
unsigned int psci_mode_shift;
unsigned int psci_mode_mask;
bool no_saw_devices;
struct cluster_history history;
struct hrtimer histtimer;
};
int set_l2_mode(struct low_power_ops *ops, int mode, bool notify_rpm);
int set_system_mode(struct low_power_ops *ops, int mode, bool notify_rpm);
int set_l3_mode(struct low_power_ops *ops, int mode, bool notify_rpm);
void lpm_suspend_wake_time(uint64_t wakeup_time);
struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev);
void free_cluster_node(struct lpm_cluster *cluster);
void cluster_dt_walkthrough(struct lpm_cluster *cluster);
int create_cluster_lvl_nodes(struct lpm_cluster *p, struct kobject *kobj);
bool lpm_cpu_mode_allow(unsigned int cpu,
unsigned int mode, bool from_idle);
bool lpm_cluster_mode_allow(struct lpm_cluster *cluster,
unsigned int mode, bool from_idle);
uint32_t *get_per_cpu_max_residency(int cpu);
uint32_t *get_per_cpu_min_residency(int cpu);
extern struct lpm_cluster *lpm_root_node;
#ifdef CONFIG_SMP
extern DEFINE_PER_CPU(bool, pending_ipi);
static inline bool is_IPI_pending(const struct cpumask *mask)
{
unsigned int cpu;
for_each_cpu(cpu, mask) {
if per_cpu(pending_ipi, cpu)
return true;
}
return false;
}
#else
static inline bool is_IPI_pending(const struct cpumask *mask)
{
return false;
}
#endif

View file

@ -540,3 +540,72 @@ config WCD_DSP_GLINK
This option enables driver which provides communication interface
between MSM and WCD DSP over glink transport protocol. This driver
provides read and write interface via char device.
config MSM_EVENT_TIMER
bool "Event timer"
help
This option enables a modules that manages a list of event timers
that need to be monitored by the PM. The enables the PM code to
monitor events that require the core to be awake and ready to
handle the event.
config MSM_PM
depends on PM
select MSM_IDLE_STATS if DEBUG_FS
select CPU_IDLE_MULTIPLE_DRIVERS
bool "Qualcomm platform specific PM driver"
help
Platform specific power driver to manage cores and l2 low power
modes. It interface with various system driver and put the cores
into low power modes. It implements OS initiated scheme and
determines last CPU to call into PSCI for cluster Low power
modes.
config MSM_NOPM
default y if !PM
bool
help
This enables bare minimum support of power management at platform level.
i.e WFI
config APSS_CORE_EA
depends on CPU_FREQ && PM_OPP
bool "Qualcomm Technology Inc specific power aware driver"
help
Platform specific power aware driver to provide power
and temperature information to the scheduler.
if MSM_PM
menuconfig MSM_IDLE_STATS
bool "Collect idle statistics"
help
Collect cores various low power mode idle statistics
and export them in proc/msm_pm_stats. User can read
this data and determine what low power modes and how
many times cores have entered into LPM modes.
if MSM_IDLE_STATS
config MSM_IDLE_STATS_FIRST_BUCKET
int "First bucket time"
default 62500
help
Upper time limit in nanoseconds of first bucket.
config MSM_IDLE_STATS_BUCKET_SHIFT
int "Bucket shift"
default 2
config MSM_IDLE_STATS_BUCKET_COUNT
int "Bucket count"
default 10
config MSM_SUSPEND_STATS_FIRST_BUCKET
int "First bucket time for suspend"
default 1000000000
help
Upper time limit in nanoseconds of first bucket of the
histogram. This is for collecting statistics on suspend.
endif # MSM_IDLE_STATS
endif # MSM_PM

View file

@ -62,3 +62,6 @@ ifdef CONFIG_MSM_SUBSYSTEM_RESTART
endif
obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
obj-$(CONFIG_WCD_DSP_GLINK) += wcd-dsp-glink.o
obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o
obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o
obj-$(CONFIG_APSS_CORE_EA) += msm-core.o debug_core.o

View file

@ -0,0 +1,330 @@
/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/debugfs.h>
#include <linux/ctype.h>
#include <linux/cpu.h>
#include "soc/qcom/msm-core.h"
#define MAX_PSTATES 50
#define NUM_OF_PENTRY 3 /* number of variables for ptable node */
#define NUM_OF_EENTRY 2 /* number of variables for enable node */
enum arg_offset {
CPU_OFFSET,
FREQ_OFFSET,
POWER_OFFSET,
};
struct core_debug {
int cpu;
struct cpu_pstate_pwr *head;
int enabled;
int len;
struct cpu_pwr_stats *ptr;
struct cpu_pstate_pwr *driver_data;
int driver_len;
};
static DEFINE_PER_CPU(struct core_debug, c_dgfs);
static struct cpu_pwr_stats *msm_core_data;
static struct debugfs_blob_wrapper help_msg = {
.data =
"MSM CORE Debug-FS Support\n"
"\n"
"Hierarchy schema\n"
"/sys/kernel/debug/msm_core\n"
" /help - Static help text\n"
" /ptable - write to p-state table\n"
" /enable - enable the written p-state table\n"
" /ptable_dump - Dump the debug ptable\n"
"\n"
"Usage\n"
" Input test frequency and power information in ptable:\n"
" echo \"0 300000 120\" > ptable\n"
" format: <cpu> <frequency in khz> <power>\n"
"\n"
" Enable the ptable for the cpu:\n"
" echo \"0 1\" > enable\n"
" format: <cpu> <1 to enable, 0 to disable>\n"
" Note: Writing 0 to disable will reset/clear the ptable\n"
"\n"
" Dump the entire ptable:\n"
" cat ptable\n"
" ----- CPU0 - Enabled ---------\n"
" Freq Power\n"
" 700000 120\n"
"----- CPU0 - Live numbers -----\n"
" Freq Power\n"
" 300000 218\n"
" ----- CPU1 - Written ---------\n"
" Freq Power\n"
" 700000 120\n"
" Ptable dump will dump the status of the table as well\n"
" It shows:\n"
" Enabled -> for a cpu that debug ptable enabled\n"
" Written -> for a cpu that has debug ptable values written\n"
" but not enabled\n"
"\n",
};
static void add_to_ptable(unsigned int *arg)
{
struct core_debug *node;
int i, cpu = arg[CPU_OFFSET];
uint32_t freq = arg[FREQ_OFFSET];
uint32_t power = arg[POWER_OFFSET];
if (!cpu_possible(cpu))
return;
if ((freq == 0) || (power == 0)) {
pr_warn("Incorrect power data\n");
return;
}
node = &per_cpu(c_dgfs, cpu);
if (node->len >= MAX_PSTATES) {
pr_warn("Dropped ptable update - no space left.\n");
return;
}
if (!node->head) {
node->head = kzalloc(sizeof(struct cpu_pstate_pwr) *
(MAX_PSTATES + 1),
GFP_KERNEL);
if (!node->head)
return;
}
for (i = 0; i < node->len; i++) {
if (node->head[i].freq == freq) {
node->head[i].power = power;
return;
}
}
/*
* Insert a new frequency (may need to move things around to
* keep in ascending order).
*/
for (i = MAX_PSTATES - 1; i > 0; i--) {
if (node->head[i-1].freq > freq) {
node->head[i].freq = node->head[i-1].freq;
node->head[i].power = node->head[i-1].power;
} else if (node->head[i-1].freq != 0) {
break;
}
}
if (node->len < MAX_PSTATES) {
node->head[i].freq = freq;
node->head[i].power = power;
node->len++;
}
if (node->ptr)
node->ptr->len = node->len;
}
static int split_ptable_args(char *line, unsigned int *arg, uint32_t n)
{
char *args;
int i;
int ret = 0;
for (i = 0; i < n; i++) {
if (!line)
break;
args = strsep(&line, " ");
ret = kstrtouint(args, 10, &arg[i]);
if (ret)
return ret;
}
return ret;
}
static ssize_t msm_core_ptable_write(struct file *file,
const char __user *ubuf, size_t len, loff_t *offp)
{
char *kbuf;
int ret;
unsigned int arg[3];
if (len == 0)
return 0;
kbuf = kzalloc(len + 1, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
if (copy_from_user(kbuf, ubuf, len)) {
ret = -EFAULT;
goto done;
}
kbuf[len] = '\0';
ret = split_ptable_args(kbuf, arg, NUM_OF_PENTRY);
if (!ret) {
add_to_ptable(arg);
ret = len;
}
done:
kfree(kbuf);
return ret;
}
static void print_table(struct seq_file *m, struct cpu_pstate_pwr *c_n,
int len)
{
int i;
seq_puts(m, " Freq Power\n");
for (i = 0; i < len; i++)
seq_printf(m, " %d %u\n", c_n[i].freq,
c_n[i].power);
}
static int msm_core_ptable_read(struct seq_file *m, void *data)
{
int cpu;
struct core_debug *node;
for_each_possible_cpu(cpu) {
node = &per_cpu(c_dgfs, cpu);
if (node->head) {
seq_printf(m, "----- CPU%d - %s - Debug -------\n",
cpu, node->enabled == 1 ? "Enabled" : "Written");
print_table(m, node->head, node->len);
}
if (msm_core_data[cpu].ptable) {
seq_printf(m, "--- CPU%d - Live numbers at %ldC---\n",
cpu, node->ptr->temp);
print_table(m, msm_core_data[cpu].ptable,
node->driver_len);
}
}
return 0;
}
static ssize_t msm_core_enable_write(struct file *file,
const char __user *ubuf, size_t len, loff_t *offp)
{
char *kbuf;
int ret;
unsigned int arg[3];
int cpu;
if (len == 0)
return 0;
kbuf = kzalloc(len + 1, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
if (copy_from_user(kbuf, ubuf, len)) {
ret = -EFAULT;
goto done;
}
kbuf[len] = '\0';
ret = split_ptable_args(kbuf, arg, NUM_OF_EENTRY);
if (ret)
goto done;
cpu = arg[CPU_OFFSET];
if (cpu_possible(cpu)) {
struct core_debug *node = &per_cpu(c_dgfs, cpu);
if (arg[FREQ_OFFSET]) {
msm_core_data[cpu].ptable = node->head;
msm_core_data[cpu].len = node->len;
} else {
msm_core_data[cpu].ptable = node->driver_data;
msm_core_data[cpu].len = node->driver_len;
node->len = 0;
}
node->enabled = arg[FREQ_OFFSET];
}
ret = len;
blocking_notifier_call_chain(
get_power_update_notifier(), cpu, NULL);
done:
kfree(kbuf);
return ret;
}
static const struct file_operations msm_core_enable_ops = {
.write = msm_core_enable_write,
};
static int msm_core_dump_open(struct inode *inode, struct file *file)
{
return single_open(file, msm_core_ptable_read, inode->i_private);
}
static const struct file_operations msm_core_ptable_ops = {
.open = msm_core_dump_open,
.read = seq_read,
.write = msm_core_ptable_write,
.llseek = seq_lseek,
.release = single_release,
};
int msm_core_debug_init(void)
{
struct dentry *dir;
struct dentry *file;
int i;
msm_core_data = get_cpu_pwr_stats();
if (!msm_core_data)
goto fail;
dir = debugfs_create_dir("msm_core", NULL);
if (IS_ERR_OR_NULL(dir))
return PTR_ERR(dir);
file = debugfs_create_file("enable", 0660, dir, NULL,
&msm_core_enable_ops);
if (IS_ERR_OR_NULL(file))
goto fail;
file = debugfs_create_file("ptable", 0660, dir, NULL,
&msm_core_ptable_ops);
if (IS_ERR_OR_NULL(file))
goto fail;
help_msg.size = strlen(help_msg.data);
file = debugfs_create_blob("help", 0444, dir, &help_msg);
if (IS_ERR_OR_NULL(file))
goto fail;
for (i = 0; i < num_possible_cpus(); i++) {
per_cpu(c_dgfs, i).ptr = &msm_core_data[i];
per_cpu(c_dgfs, i).driver_data = msm_core_data[i].ptable;
per_cpu(c_dgfs, i).driver_len = msm_core_data[i].len;
}
return 0;
fail:
debugfs_remove(dir);
return PTR_ERR(file);
}
late_initcall(msm_core_debug_init);

View file

@ -0,0 +1,503 @@
/* Copyright (c) 2012, 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/module.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/cpu.h>
#include <soc/qcom/event_timer.h>
/**
* struct event_timer_info - basic event timer structure
* @node: timerqueue node to track time ordered data structure
* of event timers
* @notify: irq affinity notifier.
* @timer: hrtimer created for this event.
* @function : callback function for event timer.
* @data : callback data for event timer.
* @irq: irq number for which event timer is created.
* @cpu: event timer associated cpu.
*/
struct event_timer_info {
struct timerqueue_node node;
struct irq_affinity_notify notify;
void (*function)(void *);
void *data;
int irq;
int cpu;
};
struct hrtimer_info {
struct hrtimer event_hrtimer;
bool timer_initialized;
};
static DEFINE_PER_CPU(struct hrtimer_info, per_cpu_hrtimer);
static DEFINE_PER_CPU(struct timerqueue_head, timer_head) = {
.head = RB_ROOT,
.next = NULL,
};
static DEFINE_SPINLOCK(event_timer_lock);
static DEFINE_SPINLOCK(event_setup_lock);
static void create_timer_smp(void *data);
static void setup_event_hrtimer(struct event_timer_info *event);
static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer);
static void irq_affinity_change_notifier(struct irq_affinity_notify *notify,
const cpumask_t *new_cpu_mask);
static void irq_affinity_release(struct kref *ref);
static int msm_event_debug_mask;
module_param_named(debug_mask, msm_event_debug_mask, int, 0664);
enum {
MSM_EVENT_TIMER_DEBUG = 1U << 0,
};
/**
* add_event_timer() : Add a wakeup event. Intended to be called
* by clients once. Returns a handle to be used
* for future transactions.
* @irq: event associated irq number.
* @function : The callback function will be called when event
* timer expires.
* @data: callback data provided by client.
*/
struct event_timer_info *add_event_timer(uint32_t irq,
void (*function)(void *), void *data)
{
struct event_timer_info *event_info =
kzalloc(sizeof(struct event_timer_info), GFP_KERNEL);
if (!event_info)
return NULL;
event_info->function = function;
event_info->data = data;
if (irq) {
struct irq_desc *desc = irq_to_desc(irq);
struct cpumask *mask = desc->irq_common_data.affinity;
get_online_cpus();
event_info->cpu = cpumask_any_and(mask, cpu_online_mask);
if (event_info->cpu >= nr_cpu_ids)
event_info->cpu = cpumask_first(cpu_online_mask);
event_info->notify.notify = irq_affinity_change_notifier;
event_info->notify.release = irq_affinity_release;
irq_set_affinity_notifier(irq, &event_info->notify);
put_online_cpus();
}
/* Init rb node and hr timer */
timerqueue_init(&event_info->node);
pr_debug("New Event Added. Event %p(on cpu%d). irq %d.\n",
event_info, event_info->cpu, irq);
return event_info;
}
EXPORT_SYMBOL(add_event_timer);
/**
* is_event_next(): Helper function to check if the event is the next
* expiring event
* @event : handle to the event to be checked.
*/
static bool is_event_next(struct event_timer_info *event)
{
struct event_timer_info *next_event;
struct timerqueue_node *next;
bool ret = false;
next = timerqueue_getnext(&per_cpu(timer_head, event->cpu));
if (!next)
goto exit_is_next_event;
next_event = container_of(next, struct event_timer_info, node);
if (!next_event)
goto exit_is_next_event;
if (next_event == event)
ret = true;
exit_is_next_event:
return ret;
}
/**
* is_event_active(): Helper function to check if the timer for a given event
* has been started.
* @event : handle to the event to be checked.
*/
static bool is_event_active(struct event_timer_info *event)
{
struct timerqueue_node *next;
struct event_timer_info *next_event;
bool ret = false;
for (next = timerqueue_getnext(&per_cpu(timer_head, event->cpu)); next;
next = timerqueue_iterate_next(next)) {
next_event = container_of(next, struct event_timer_info, node);
if (event == next_event) {
ret = true;
break;
}
}
return ret;
}
/**
* create_hrtimer(): Helper function to setup hrtimer.
*/
static void create_hrtimer(struct event_timer_info *event)
{
bool timer_initialized = per_cpu(per_cpu_hrtimer.timer_initialized,
event->cpu);
struct hrtimer *event_hrtimer = &per_cpu(per_cpu_hrtimer.event_hrtimer,
event->cpu);
if (!timer_initialized) {
hrtimer_init(event_hrtimer, CLOCK_MONOTONIC,
HRTIMER_MODE_ABS_PINNED);
per_cpu(per_cpu_hrtimer.timer_initialized, event->cpu) = true;
}
event_hrtimer->function = event_hrtimer_cb;
hrtimer_start(event_hrtimer, event->node.expires,
HRTIMER_MODE_ABS_PINNED);
}
/**
* event_hrtimer_cb() : Callback function for hr timer.
* Make the client CB from here and remove the event
* from the time ordered queue.
*/
static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer)
{
struct event_timer_info *event;
struct timerqueue_node *next;
unsigned long flags;
int cpu;
spin_lock_irqsave(&event_timer_lock, flags);
cpu = smp_processor_id();
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
while (next && (ktime_to_ns(next->expires)
<= ktime_to_ns(hrtimer->node.expires))) {
event = container_of(next, struct event_timer_info, node);
if (!event)
goto hrtimer_cb_exit;
WARN_ON_ONCE(event->cpu != cpu);
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Deleting event %p @ %lu(on cpu%d)\n", event,
(unsigned long)ktime_to_ns(next->expires), cpu);
timerqueue_del(&per_cpu(timer_head, cpu), &event->node);
if (event->function)
event->function(event->data);
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
}
if (next) {
event = container_of(next, struct event_timer_info, node);
create_hrtimer(event);
}
hrtimer_cb_exit:
spin_unlock_irqrestore(&event_timer_lock, flags);
return HRTIMER_NORESTART;
}
/**
* create_timer_smp(): Helper function used setting up timer on CPUs.
*/
static void create_timer_smp(void *data)
{
unsigned long flags;
struct event_timer_info *event =
(struct event_timer_info *)data;
struct timerqueue_node *next;
spin_lock_irqsave(&event_timer_lock, flags);
if (is_event_active(event))
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
next = timerqueue_getnext(&per_cpu(timer_head, event->cpu));
timerqueue_add(&per_cpu(timer_head, event->cpu), &event->node);
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Adding Event %p(on cpu%d) for %lu\n", event,
event->cpu,
(unsigned long)ktime_to_ns(event->node.expires));
if (!next || (next && (ktime_to_ns(event->node.expires) <
ktime_to_ns(next->expires)))) {
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Setting timer for %lu(on cpu%d)\n",
(unsigned long)ktime_to_ns(event->node.expires),
event->cpu);
create_hrtimer(event);
}
spin_unlock_irqrestore(&event_timer_lock, flags);
}
/**
* setup_timer() : Helper function to setup timer on primary
* core during hrtimer callback.
* @event: event handle causing the wakeup.
*/
static void setup_event_hrtimer(struct event_timer_info *event)
{
smp_call_function_single(event->cpu, create_timer_smp, event, 1);
}
static void irq_affinity_release(struct kref *ref)
{
struct event_timer_info *event;
struct irq_affinity_notify *notify =
container_of(ref, struct irq_affinity_notify, kref);
event = container_of(notify, struct event_timer_info, notify);
pr_debug("event = %p\n", event);
}
static void irq_affinity_change_notifier(struct irq_affinity_notify *notify,
const cpumask_t *mask_val)
{
struct event_timer_info *event;
unsigned long flags;
unsigned int irq;
int old_cpu = -EINVAL, new_cpu = -EINVAL;
bool next_event = false;
event = container_of(notify, struct event_timer_info, notify);
irq = notify->irq;
if (!event)
return;
/*
* This logic is inline with irq-gic.c for finding
* the next affinity CPU.
*/
new_cpu = cpumask_any_and(mask_val, cpu_online_mask);
if (new_cpu >= nr_cpu_ids)
return;
old_cpu = event->cpu;
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("irq %d, event %p, old_cpu(%d)->new_cpu(%d).\n",
irq, event, old_cpu, new_cpu);
/* No change in IRQ affinity */
if (old_cpu == new_cpu)
return;
spin_lock_irqsave(&event_timer_lock, flags);
/* If the event is not active OR
* If it is the next event
* and the timer is already in callback
* Just reset cpu and return
*/
if (!is_event_active(event) ||
(is_event_next(event) &&
(hrtimer_try_to_cancel(&per_cpu(per_cpu_hrtimer.
event_hrtimer, old_cpu)) < 0))) {
event->cpu = new_cpu;
spin_unlock_irqrestore(&event_timer_lock, flags);
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Event:%p is not active or in callback\n",
event);
return;
}
/* Update the flag based on EVENT is next are not */
if (is_event_next(event))
next_event = true;
event->cpu = new_cpu;
/*
* We are here either because hrtimer was active or event is not next
* Delete the event from the timer queue anyway
*/
timerqueue_del(&per_cpu(timer_head, old_cpu), &event->node);
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Event:%p is in the list\n", event);
spin_unlock_irqrestore(&event_timer_lock, flags);
/*
* Migrating event timer to a new CPU is automatically
* taken care. Since we have already modify the event->cpu
* with new CPU.
*
* Typical cases are
*
* 1)
* C0 C1
* | ^
* ----------------- |
* | | | |
* E1 E2 E3 |
* |(migrating) |
* -------------------------
*
* 2)
* C0 C1
* | ^
* ---------------- |
* | | | |
* E1 E2 E3 |
* |(migrating) |
* ---------------------------------
*
* Here after moving the E1 to C1. Need to start
* E2 on C0.
*/
spin_lock(&event_setup_lock);
/* Setup event timer on new cpu*/
setup_event_hrtimer(event);
/* Setup event on the old cpu*/
if (next_event) {
struct timerqueue_node *next;
next = timerqueue_getnext(&per_cpu(timer_head, old_cpu));
if (next) {
event = container_of(next,
struct event_timer_info, node);
setup_event_hrtimer(event);
}
}
spin_unlock(&event_setup_lock);
}
/**
* activate_event_timer() : Set the expiration time for an event in absolute
* ktime. This is a oneshot event timer, clients
* should call this again to set another expiration.
* @event : event handle.
* @event_time : event time in absolute ktime.
*/
void activate_event_timer(struct event_timer_info *event, ktime_t event_time)
{
if (!event)
return;
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Adding event %p timer @ %lu(on cpu%d)\n", event,
(unsigned long)ktime_to_us(event_time),
event->cpu);
spin_lock(&event_setup_lock);
event->node.expires = event_time;
/* Start hrtimer and add event to rb tree */
setup_event_hrtimer(event);
spin_unlock(&event_setup_lock);
}
EXPORT_SYMBOL(activate_event_timer);
/**
* deactivate_event_timer() : Deactivate an event timer, this removes the event
* from the time ordered queue of event timers.
* @event: event handle.
*/
void deactivate_event_timer(struct event_timer_info *event)
{
unsigned long flags;
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Deactivate timer\n");
spin_lock_irqsave(&event_timer_lock, flags);
if (is_event_active(event)) {
if (is_event_next(event))
hrtimer_try_to_cancel(&per_cpu(
per_cpu_hrtimer.event_hrtimer, event->cpu));
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
}
spin_unlock_irqrestore(&event_timer_lock, flags);
}
/**
* destroy_event_timer() : Free the event info data structure allocated during
* add_event_timer().
* @event: event handle.
*/
void destroy_event_timer(struct event_timer_info *event)
{
unsigned long flags;
spin_lock_irqsave(&event_timer_lock, flags);
if (is_event_active(event)) {
if (is_event_next(event))
hrtimer_try_to_cancel(&per_cpu(
per_cpu_hrtimer.event_hrtimer, event->cpu));
timerqueue_del(&per_cpu(timer_head, event->cpu), &event->node);
}
spin_unlock_irqrestore(&event_timer_lock, flags);
kfree(event);
}
EXPORT_SYMBOL(destroy_event_timer);
/**
* get_next_event_timer() - Get the next wakeup event. Returns
* a ktime value of the next expiring event.
*/
ktime_t get_next_event_time(int cpu)
{
unsigned long flags;
struct timerqueue_node *next;
struct event_timer_info *event;
ktime_t next_event = ns_to_ktime(0);
spin_lock_irqsave(&event_timer_lock, flags);
next = timerqueue_getnext(&per_cpu(timer_head, cpu));
event = container_of(next, struct event_timer_info, node);
spin_unlock_irqrestore(&event_timer_lock, flags);
if (!next || event->cpu != cpu)
return next_event;
next_event = hrtimer_get_remaining(
&per_cpu(per_cpu_hrtimer.event_hrtimer, cpu));
if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG)
pr_debug("Next Event %lu(on cpu%d)\n",
(unsigned long)ktime_to_us(next_event), cpu);
return next_event;
}

View file

@ -0,0 +1,864 @@
/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/debugfs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <soc/qcom/spm.h>
#include <soc/qcom/pm.h>
#include <soc/qcom/lpm-stats.h>
#define MAX_STR_LEN 256
#define MAX_TIME_LEN 20
const char *lpm_stats_reset = "reset";
const char *lpm_stats_suspend = "suspend";
struct lpm_sleep_time {
struct kobj_attribute ts_attr;
unsigned int cpu;
};
struct level_stats {
const char *name;
struct lpm_stats *owner;
int64_t first_bucket_time;
int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT];
int success_count;
int failed_count;
int64_t total_time;
uint64_t enter_time;
};
static struct level_stats suspend_time_stats;
static DEFINE_PER_CPU_SHARED_ALIGNED(struct lpm_stats, cpu_stats);
static uint64_t get_total_sleep_time(unsigned int cpu_id)
{
struct lpm_stats *stats = &per_cpu(cpu_stats, cpu_id);
int i;
uint64_t ret = 0;
for (i = 0; i < stats->num_levels; i++)
ret += stats->time_stats[i].total_time;
return ret;
}
static void update_level_stats(struct level_stats *stats, uint64_t t,
bool success)
{
uint64_t bt;
int i;
if (!success) {
stats->failed_count++;
return;
}
stats->success_count++;
stats->total_time += t;
bt = t;
do_div(bt, stats->first_bucket_time);
if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT *
(CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1)))
i = DIV_ROUND_UP(fls((uint32_t)bt),
CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT);
else
i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT)
i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
stats->bucket[i]++;
if (t < stats->min_time[i] || !stats->max_time[i])
stats->min_time[i] = t;
if (t > stats->max_time[i])
stats->max_time[i] = t;
}
static void level_stats_print(struct seq_file *m, struct level_stats *stats)
{
int i = 0;
int64_t bucket_time = 0;
char seqs[MAX_STR_LEN] = {0};
int64_t s = stats->total_time;
uint32_t ns = do_div(s, NSEC_PER_SEC);
snprintf(seqs, MAX_STR_LEN,
"[%s] %s:\n"
" success count: %7d\n"
" total success time: %lld.%09u\n",
stats->owner->name,
stats->name,
stats->success_count,
s, ns);
seq_puts(m, seqs);
if (stats->failed_count) {
snprintf(seqs, MAX_STR_LEN, " failed count: %7d\n",
stats->failed_count);
seq_puts(m, seqs);
}
bucket_time = stats->first_bucket_time;
for (i = 0;
i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1;
i++) {
s = bucket_time;
ns = do_div(s, NSEC_PER_SEC);
snprintf(seqs, MAX_STR_LEN,
"\t<%6lld.%09u: %7d (%lld-%lld)\n",
s, ns, stats->bucket[i],
stats->min_time[i],
stats->max_time[i]);
seq_puts(m, seqs);
bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT;
}
snprintf(seqs, MAX_STR_LEN,
"\t>=%5lld.%09u:%8d (%lld-%lld)\n",
s, ns, stats->bucket[i],
stats->min_time[i],
stats->max_time[i]);
seq_puts(m, seqs);
}
static int level_stats_file_show(struct seq_file *m, void *v)
{
struct level_stats *stats = NULL;
if (!m->private)
return -EINVAL;
stats = (struct level_stats *) m->private;
level_stats_print(m, stats);
return 0;
}
static int level_stats_file_open(struct inode *inode, struct file *file)
{
return single_open(file, level_stats_file_show, inode->i_private);
}
static void level_stats_print_all(struct seq_file *m, struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
int i = 0;
for (i = 0; i < stats->num_levels; i++)
level_stats_print(m, &stats->time_stats[i]);
if (list_empty(&stats->child))
return;
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
level_stats_print_all(m, pos);
}
}
static void level_stats_reset(struct level_stats *stats)
{
memset(stats->bucket, 0, sizeof(stats->bucket));
memset(stats->min_time, 0, sizeof(stats->min_time));
memset(stats->max_time, 0, sizeof(stats->max_time));
stats->success_count = 0;
stats->failed_count = 0;
stats->total_time = 0;
}
static void level_stats_reset_all(struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
int i = 0;
for (i = 0; i < stats->num_levels; i++)
level_stats_reset(&stats->time_stats[i]);
if (list_empty(&stats->child))
return;
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
level_stats_reset_all(pos);
}
}
static int lpm_stats_file_show(struct seq_file *m, void *v)
{
struct lpm_stats *stats = (struct lpm_stats *)m->private;
if (!m->private) {
pr_err("%s: Invalid pdata, Cannot print stats\n", __func__);
return -EINVAL;
}
level_stats_print_all(m, stats);
level_stats_print(m, &suspend_time_stats);
return 0;
}
static int lpm_stats_file_open(struct inode *inode, struct file *file)
{
return single_open(file, lpm_stats_file_show, inode->i_private);
}
static ssize_t level_stats_file_write(struct file *file,
const char __user *buffer, size_t count, loff_t *off)
{
char buf[MAX_STR_LEN] = {0};
struct inode *in = file->f_inode;
struct level_stats *stats = (struct level_stats *)in->i_private;
size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
if (!stats)
return -EINVAL;
if (count != len+1)
return -EINVAL;
if (copy_from_user(buf, buffer, len))
return -EFAULT;
if (strcmp(buf, lpm_stats_reset))
return -EINVAL;
level_stats_reset(stats);
return count;
}
static ssize_t lpm_stats_file_write(struct file *file,
const char __user *buffer, size_t count, loff_t *off)
{
char buf[MAX_STR_LEN] = {0};
struct inode *in = file->f_inode;
struct lpm_stats *stats = (struct lpm_stats *)in->i_private;
size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
if (!stats)
return -EINVAL;
if (count != len+1)
return -EINVAL;
if (copy_from_user(buf, buffer, len))
return -EFAULT;
if (strcmp(buf, lpm_stats_reset))
return -EINVAL;
level_stats_reset_all(stats);
return count;
}
int lifo_stats_file_show(struct seq_file *m, void *v)
{
struct lpm_stats *stats = NULL;
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
char seqs[MAX_STR_LEN] = {0};
if (!m->private)
return -EINVAL;
stats = (struct lpm_stats *)m->private;
if (list_empty(&stats->child)) {
pr_err("%s: ERROR: Lifo level with no children.\n",
__func__);
return -EINVAL;
}
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
snprintf(seqs, MAX_STR_LEN,
"%s:\n"
"\tLast-In:%u\n"
"\tFirst-Out:%u\n",
pos->name,
pos->lifo.last_in,
pos->lifo.first_out);
seq_puts(m, seqs);
}
return 0;
}
static int lifo_stats_file_open(struct inode *inode, struct file *file)
{
return single_open(file, lifo_stats_file_show, inode->i_private);
}
static void lifo_stats_reset_all(struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
pos->lifo.last_in = 0;
pos->lifo.first_out = 0;
if (!list_empty(&pos->child))
lifo_stats_reset_all(pos);
}
}
static ssize_t lifo_stats_file_write(struct file *file,
const char __user *buffer, size_t count, loff_t *off)
{
char buf[MAX_STR_LEN] = {0};
struct inode *in = file->f_inode;
struct lpm_stats *stats = (struct lpm_stats *)in->i_private;
size_t len = strnlen(lpm_stats_reset, MAX_STR_LEN);
if (!stats)
return -EINVAL;
if (count != len+1)
return -EINVAL;
if (copy_from_user(buf, buffer, len))
return -EFAULT;
if (strcmp(buf, lpm_stats_reset))
return -EINVAL;
lifo_stats_reset_all(stats);
return count;
}
static const struct file_operations level_stats_fops = {
.owner = THIS_MODULE,
.open = level_stats_file_open,
.read = seq_read,
.release = single_release,
.llseek = no_llseek,
.write = level_stats_file_write,
};
static const struct file_operations lpm_stats_fops = {
.owner = THIS_MODULE,
.open = lpm_stats_file_open,
.read = seq_read,
.release = single_release,
.llseek = no_llseek,
.write = lpm_stats_file_write,
};
static const struct file_operations lifo_stats_fops = {
.owner = THIS_MODULE,
.open = lifo_stats_file_open,
.read = seq_read,
.release = single_release,
.llseek = no_llseek,
.write = lifo_stats_file_write,
};
static void update_last_in_stats(struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
if (list_empty(&stats->child))
return;
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
if (cpumask_test_cpu(smp_processor_id(), &pos->mask)) {
pos->lifo.last_in++;
return;
}
}
WARN(1, "Should not reach here\n");
}
static void update_first_out_stats(struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
if (list_empty(&stats->child))
return;
centry = &stats->child;
list_for_each_entry(pos, centry, sibling) {
if (cpumask_test_cpu(smp_processor_id(), &pos->mask)) {
pos->lifo.first_out++;
return;
}
}
WARN(1, "Should not reach here\n");
}
static inline void update_exit_stats(struct lpm_stats *stats, uint32_t index,
bool success)
{
uint64_t exit_time = 0;
/* Update time stats only when exit is preceded by enter */
exit_time = stats->sleep_time;
update_level_stats(&stats->time_stats[index], exit_time,
success);
}
static int config_level(const char *name, const char **levels,
int num_levels, struct lpm_stats *parent, struct lpm_stats *stats)
{
int i = 0;
struct dentry *directory = NULL;
const char *rootname = "lpm_stats";
const char *dirname = rootname;
strlcpy(stats->name, name, MAX_STR_LEN);
stats->num_levels = num_levels;
stats->parent = parent;
INIT_LIST_HEAD(&stats->sibling);
INIT_LIST_HEAD(&stats->child);
stats->time_stats = kcalloc(num_levels, sizeof(struct level_stats),
GFP_KERNEL);
if (!stats->time_stats)
return -ENOMEM;
if (parent) {
list_add_tail(&stats->sibling, &parent->child);
directory = parent->directory;
dirname = name;
}
stats->directory = debugfs_create_dir(dirname, directory);
if (!stats->directory) {
pr_err("%s: Unable to create %s debugfs directory\n",
__func__, dirname);
kfree(stats->time_stats);
return -EPERM;
}
for (i = 0; i < num_levels; i++) {
stats->time_stats[i].name = levels[i];
stats->time_stats[i].owner = stats;
stats->time_stats[i].first_bucket_time =
CONFIG_MSM_IDLE_STATS_FIRST_BUCKET;
stats->time_stats[i].enter_time = 0;
if (!debugfs_create_file(stats->time_stats[i].name, 0444,
stats->directory, (void *)&stats->time_stats[i],
&level_stats_fops)) {
pr_err("%s: Unable to create %s %s level-stats file\n",
__func__, stats->name,
stats->time_stats[i].name);
kfree(stats->time_stats);
return -EPERM;
}
}
if (!debugfs_create_file("stats", 0444, stats->directory,
(void *)stats, &lpm_stats_fops)) {
pr_err("%s: Unable to create %s's overall 'stats' file\n",
__func__, stats->name);
kfree(stats->time_stats);
return -EPERM;
}
return 0;
}
static ssize_t total_sleep_time_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct lpm_sleep_time *cpu_sleep_time = container_of(attr,
struct lpm_sleep_time, ts_attr);
unsigned int cpu = cpu_sleep_time->cpu;
uint64_t total_time = get_total_sleep_time(cpu);
return snprintf(buf, MAX_TIME_LEN, "%llu.%09u\n", total_time,
do_div(total_time, NSEC_PER_SEC));
}
static struct kobject *local_module_kobject(void)
{
struct kobject *kobj;
kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
if (!kobj) {
int err;
struct module_kobject *mk;
mk = kzalloc(sizeof(*mk), GFP_KERNEL);
if (!mk)
return ERR_PTR(-ENOMEM);
mk->mod = THIS_MODULE;
mk->kobj.kset = module_kset;
err = kobject_init_and_add(&mk->kobj, &module_ktype, NULL,
"%s", KBUILD_MODNAME);
if (err) {
kobject_put(&mk->kobj);
kfree(mk);
pr_err("%s: cannot create kobject for %s\n",
__func__, KBUILD_MODNAME);
return ERR_PTR(err);
}
kobject_get(&mk->kobj);
kobj = &mk->kobj;
}
return kobj;
}
static int create_sysfs_node(unsigned int cpu, struct lpm_stats *stats)
{
struct kobject *cpu_kobj = NULL;
struct lpm_sleep_time *ts = NULL;
struct kobject *stats_kobj;
char cpu_name[] = "cpuXX";
int ret = -ENOMEM;
stats_kobj = local_module_kobject();
if (IS_ERR_OR_NULL(stats_kobj))
return PTR_ERR(stats_kobj);
snprintf(cpu_name, sizeof(cpu_name), "cpu%u", cpu);
cpu_kobj = kobject_create_and_add(cpu_name, stats_kobj);
if (!cpu_kobj)
return -ENOMEM;
ts = kzalloc(sizeof(*ts), GFP_KERNEL);
if (!ts)
goto failed;
sysfs_attr_init(&ts->ts_attr.attr);
ts->ts_attr.attr.name = "total_sleep_time_secs";
ts->ts_attr.attr.mode = 0444;
ts->ts_attr.show = total_sleep_time_show;
ts->ts_attr.store = NULL;
ts->cpu = cpu;
ret = sysfs_create_file(cpu_kobj, &ts->ts_attr.attr);
if (ret)
goto failed;
return 0;
failed:
kfree(ts);
kobject_put(cpu_kobj);
return ret;
}
static struct lpm_stats *config_cpu_level(const char *name,
const char **levels, int num_levels, struct lpm_stats *parent,
struct cpumask *mask)
{
int cpu = 0;
struct lpm_stats *pstats = NULL;
struct lpm_stats *stats = NULL;
for (pstats = parent; pstats; pstats = pstats->parent)
cpumask_or(&pstats->mask, &pstats->mask, mask);
for_each_cpu(cpu, mask) {
int ret = 0;
char cpu_name[MAX_STR_LEN] = {0};
stats = &per_cpu(cpu_stats, cpu);
snprintf(cpu_name, MAX_STR_LEN, "%s%d", name, cpu);
cpumask_set_cpu(cpu, &stats->mask);
stats->is_cpu = true;
ret = config_level(cpu_name, levels, num_levels, parent,
stats);
if (ret) {
pr_err("%s: Unable to create %s stats\n",
__func__, cpu_name);
return ERR_PTR(ret);
}
ret = create_sysfs_node(cpu, stats);
if (ret) {
pr_err("Could not create the sysfs node\n");
return ERR_PTR(ret);
}
}
return stats;
}
static void config_suspend_level(struct lpm_stats *stats)
{
suspend_time_stats.name = lpm_stats_suspend;
suspend_time_stats.owner = stats;
suspend_time_stats.first_bucket_time =
CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET;
suspend_time_stats.enter_time = 0;
suspend_time_stats.success_count = 0;
suspend_time_stats.failed_count = 0;
if (!debugfs_create_file(suspend_time_stats.name, 0444,
stats->directory, (void *)&suspend_time_stats,
&level_stats_fops))
pr_err("%s: Unable to create %s Suspend stats file\n",
__func__, stats->name);
}
static struct lpm_stats *config_cluster_level(const char *name,
const char **levels, int num_levels, struct lpm_stats *parent)
{
struct lpm_stats *stats = NULL;
int ret = 0;
stats = kzalloc(sizeof(struct lpm_stats), GFP_KERNEL);
if (!stats)
return ERR_PTR(-ENOMEM);
stats->is_cpu = false;
ret = config_level(name, levels, num_levels, parent, stats);
if (ret) {
pr_err("%s: Unable to create %s stats\n", __func__,
name);
kfree(stats);
return ERR_PTR(ret);
}
if (!debugfs_create_file("lifo", 0444, stats->directory,
(void *)stats, &lifo_stats_fops)) {
pr_err("%s: Unable to create %s lifo stats file\n",
__func__, stats->name);
kfree(stats);
return ERR_PTR(-EPERM);
}
if (!parent)
config_suspend_level(stats);
return stats;
}
static void cleanup_stats(struct lpm_stats *stats)
{
struct list_head *centry = NULL;
struct lpm_stats *pos = NULL;
centry = &stats->child;
list_for_each_entry_reverse(pos, centry, sibling) {
if (!list_empty(&pos->child))
cleanup_stats(pos);
list_del_init(&pos->child);
kfree(pos->time_stats);
if (!pos->is_cpu)
kfree(pos);
}
kfree(stats->time_stats);
kfree(stats);
}
static void lpm_stats_cleanup(struct lpm_stats *stats)
{
struct lpm_stats *pstats = stats;
if (!pstats)
return;
while (pstats->parent)
pstats = pstats->parent;
debugfs_remove_recursive(pstats->directory);
cleanup_stats(pstats);
}
/**
* lpm_stats_config_level() - API to configure levels stats.
*
* @name: Name of the cluster/cpu.
* @levels: Low power mode level names.
* @num_levels: Number of leves supported.
* @parent: Pointer to the parent's lpm_stats object.
* @mask: cpumask, if configuring cpu stats, else NULL.
*
* Function to communicate the low power mode levels supported by
* cpus or a cluster.
*
* Return: Pointer to the lpm_stats object or ERR_PTR(-ERRNO)
*/
struct lpm_stats *lpm_stats_config_level(const char *name,
const char **levels, int num_levels, struct lpm_stats *parent,
struct cpumask *mask)
{
struct lpm_stats *stats = NULL;
if (!levels || num_levels <= 0 || IS_ERR(parent)) {
pr_err("%s: Invalid input\n\t\tlevels = %p\n\t\t"
"num_levels = %d\n\t\tparent = %ld\n",
__func__, levels, num_levels, PTR_ERR(parent));
return ERR_PTR(-EINVAL);
}
if (mask)
stats = config_cpu_level(name, levels, num_levels, parent,
mask);
else
stats = config_cluster_level(name, levels, num_levels,
parent);
if (IS_ERR(stats)) {
lpm_stats_cleanup(parent);
return stats;
}
return stats;
}
EXPORT_SYMBOL(lpm_stats_config_level);
/**
* lpm_stats_cluster_enter() - API to communicate the lpm level a cluster
* is prepared to enter.
*
* @stats: Pointer to the cluster's lpm_stats object.
* @index: Index of the lpm level that the cluster is going to enter.
*
* Function to communicate the low power mode level that the cluster is
* prepared to enter.
*/
void lpm_stats_cluster_enter(struct lpm_stats *stats, uint32_t index)
{
if (IS_ERR_OR_NULL(stats))
return;
update_last_in_stats(stats);
}
EXPORT_SYMBOL(lpm_stats_cluster_enter);
/**
* lpm_stats_cluster_exit() - API to communicate the lpm level a cluster
* exited.
*
* @stats: Pointer to the cluster's lpm_stats object.
* @index: Index of the cluster lpm level.
* @success: Success/Failure of the low power mode execution.
*
* Function to communicate the low power mode level that the cluster
* exited.
*/
void lpm_stats_cluster_exit(struct lpm_stats *stats, uint32_t index,
bool success)
{
if (IS_ERR_OR_NULL(stats))
return;
update_exit_stats(stats, index, success);
update_first_out_stats(stats);
}
EXPORT_SYMBOL(lpm_stats_cluster_exit);
/**
* lpm_stats_cpu_enter() - API to communicate the lpm level a cpu
* is prepared to enter.
*
* @index: cpu's lpm level index.
*
* Function to communicate the low power mode level that the cpu is
* prepared to enter.
*/
void lpm_stats_cpu_enter(uint32_t index, uint64_t time)
{
struct lpm_stats *stats = &(*this_cpu_ptr(&(cpu_stats)));
stats->sleep_time = time;
if (!stats->time_stats)
return;
}
EXPORT_SYMBOL(lpm_stats_cpu_enter);
/**
* lpm_stats_cpu_exit() - API to communicate the lpm level that the cpu exited.
*
* @index: cpu's lpm level index.
* @success: Success/Failure of the low power mode execution.
*
* Function to communicate the low power mode level that the cpu exited.
*/
void lpm_stats_cpu_exit(uint32_t index, uint64_t time, bool success)
{
struct lpm_stats *stats = &(*this_cpu_ptr(&(cpu_stats)));
if (!stats->time_stats)
return;
stats->sleep_time = time - stats->sleep_time;
update_exit_stats(stats, index, success);
}
EXPORT_SYMBOL(lpm_stats_cpu_exit);
/**
* lpm_stats_suspend_enter() - API to communicate system entering suspend.
*
* Function to communicate that the system is ready to enter suspend.
*/
void lpm_stats_suspend_enter(void)
{
struct timespec ts;
getnstimeofday(&ts);
suspend_time_stats.enter_time = timespec_to_ns(&ts);
}
EXPORT_SYMBOL(lpm_stats_suspend_enter);
/**
* lpm_stats_suspend_exit() - API to communicate system exiting suspend.
*
* Function to communicate that the system exited suspend.
*/
void lpm_stats_suspend_exit(void)
{
struct timespec ts;
uint64_t exit_time = 0;
getnstimeofday(&ts);
exit_time = timespec_to_ns(&ts) - suspend_time_stats.enter_time;
update_level_stats(&suspend_time_stats, exit_time, true);
}
EXPORT_SYMBOL(lpm_stats_suspend_exit);

1129
drivers/soc/qcom/msm-core.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,80 @@
/* Copyright (c) 2012, 2014,2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __ARCH_ARM_MACH_MSM_EVENT_TIMER_H
#define __ARCH_ARM_MACH_MSM_EVENT_TIMER_H
#include <linux/hrtimer.h>
struct event_timer_info;
#ifdef CONFIG_MSM_EVENT_TIMER
/**
* add_event_timer() : Add a wakeup event. Intended to be called
* by clients once. Returns a handle to be used
* for future transactions.
* @irq : Interrupt number to track affinity.
* @function : The callback function will be called when event
* timer expires.
* @data : Callback data provided by client.
*/
struct event_timer_info *add_event_timer(uint32_t irq,
void (*function)(void *), void *data);
/** activate_event_timer() : Set the expiration time for an event in absolute
* ktime. This is a oneshot event timer, clients
* should call this again to set another expiration.
* @event : Event handle.
* @event_time : Event time in absolute ktime.
*/
void activate_event_timer(struct event_timer_info *event, ktime_t event_time);
/**
* deactivate_event_timer() : Deactivate an event timer.
* @event: event handle.
*/
void deactivate_event_timer(struct event_timer_info *event);
/**
* destroy_event_timer() : Free the event info data structure allocated during
* add_event_timer().
* @event: event handle.
*/
void destroy_event_timer(struct event_timer_info *event);
/**
* get_next_event_timer() : Get the next wakeup event.
* returns a ktime value of the next
* expiring event.
*/
ktime_t get_next_event_time(int cpu);
#else
static inline void *add_event_timer(uint32_t irq, void (*function)(void *),
void *data)
{
return NULL;
}
static inline void activate_event_timer(void *event, ktime_t event_time) {}
static inline void deactivate_event_timer(void *event) {}
static inline void destroy_event_timer(void *event) {}
static inline ktime_t get_next_event_time(int cpu)
{
return ns_to_ktime(0);
}
#endif /* CONFIG_MSM_EVENT_TIMER_MANAGER */
#endif /* __ARCH_ARM_MACH_MSM_EVENT_TIMER_H */

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2014-2015,2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __ARCH_ARM_MACH_MSM_LPM_STATS_H
#define __ARCH_ARM_MACH_MSM_LPM_STATS_H
struct lpm_stats;
#define MAX_STR_LEN 256
struct lifo_stats {
uint32_t last_in;
uint32_t first_out;
};
struct lpm_stats {
char name[MAX_STR_LEN];
struct level_stats *time_stats;
uint32_t num_levels;
struct lifo_stats lifo;
struct lpm_stats *parent;
struct list_head sibling;
struct list_head child;
struct cpumask mask;
struct dentry *directory;
int64_t sleep_time;
bool is_cpu;
};
#ifdef CONFIG_MSM_IDLE_STATS
struct lpm_stats *lpm_stats_config_level(const char *name,
const char **levels, int num_levels, struct lpm_stats *parent,
struct cpumask *mask);
void lpm_stats_cluster_enter(struct lpm_stats *stats, uint32_t index);
void lpm_stats_cluster_exit(struct lpm_stats *stats, uint32_t index,
bool success);
void lpm_stats_cpu_enter(uint32_t index, uint64_t time);
void lpm_stats_cpu_exit(uint32_t index, uint64_t time, bool success);
void lpm_stats_suspend_enter(void);
void lpm_stats_suspend_exit(void);
#else
static inline struct lpm_stats *lpm_stats_config_level(const char *name,
const char **levels, int num_levels, struct lpm_stats *parent,
struct cpumask *mask)
{
return ERR_PTR(-ENODEV);
}
static inline void lpm_stats_cluster_enter(struct lpm_stats *stats,
uint32_t index)
{ }
static inline void lpm_stats_cluster_exit(struct lpm_stats *stats,
uint32_t index, bool success)
{ }
static inline void lpm_stats_cpu_enter(uint32_t index, uint64_t time)
{ }
static inline void lpm_stats_cpu_exit(uint32_t index, bool success,
uint64_t time)
{ }
static inline void lpm_stats_suspend_enter(void)
{ }
static inline void lpm_stats_suspend_exit(void)
{ }
#endif
#endif /* __ARCH_ARM_MACH_MSM_LPM_STATS_H */

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2014-2015,2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef __ARCH_ARM_MACH_MSM_CORE_H
#define __ARCH_ARM_MACH_MSM_CORE_H
#ifdef CONFIG_APSS_CORE_EA
void set_cpu_throttled(struct cpumask *mask, bool throttling);
struct blocking_notifier_head *get_power_update_notifier(void);
#else
static inline void set_cpu_throttled(struct cpumask *mask, bool throttling) {}
struct blocking_notifier_head *get_power_update_notifier(void) {return NULL; }
#endif
#endif

214
include/soc/qcom/pm.h Normal file
View file

@ -0,0 +1,214 @@
/*
* Copyright (C) 2007 Google, Inc.
* Copyright (c) 2009-2016, The Linux Foundation. All rights reserved.
* Author: San Mehat <san@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __ARCH_ARM_MACH_MSM_PM_H
#define __ARCH_ARM_MACH_MSM_PM_H
#include <linux/types.h>
#include <linux/cpuidle.h>
#include <asm/smp_plat.h>
#include <asm/barrier.h>
#include <dt-bindings/msm/pm.h>
#if !defined(CONFIG_SMP)
#define msm_secondary_startup NULL
#elif defined(CONFIG_CPU_V7)
#define msm_secondary_startup secondary_startup
#else
#define msm_secondary_startup secondary_holding_pen
#endif
enum msm_pm_sleep_mode {
MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT,
MSM_PM_SLEEP_MODE_RETENTION,
MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE,
MSM_PM_SLEEP_MODE_POWER_COLLAPSE,
MSM_PM_SLEEP_MODE_FASTPC,
MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND,
MSM_PM_SLEEP_MODE_NR,
MSM_PM_SLEEP_MODE_NOT_SELECTED,
};
enum msm_pm_l2_scm_flag {
MSM_SCM_L2_ON = 0,
MSM_SCM_L2_OFF = 1,
MSM_SCM_L2_GDHS = 3,
MSM_SCM_L3_PC_OFF = 4,
};
#define MSM_PM_MODE(cpu, mode_nr) ((cpu) *MSM_PM_SLEEP_MODE_NR + (mode_nr))
struct msm_pm_time_params {
uint32_t latency_us;
uint32_t sleep_us;
uint32_t next_event_us;
uint32_t modified_time_us;
};
struct msm_pm_sleep_status_data {
void *base_addr;
uint32_t mask;
};
struct latency_level {
int affinity_level;
int reset_level;
const char *level_name;
};
/**
* lpm_cpu_pre_pc_cb(): API to get the L2 flag to pass to TZ
*
* @cpu: cpuid of the CPU going down.
*
* Returns the l2 flush flag enum that is passed down to TZ during power
* collaps
*/
enum msm_pm_l2_scm_flag lpm_cpu_pre_pc_cb(unsigned int cpu);
/**
* msm_pm_sleep_mode_allow() - API to determine if sleep mode is allowed.
* @cpu: CPU on which to check for the sleep mode.
* @mode: Sleep Mode to check for.
* @idle: Idle or Suspend Sleep Mode.
*
* Helper function to determine if a Idle or Suspend
* Sleep mode is allowed for a specific CPU.
*
* Return: 1 for allowed; 0 if not allowed.
*/
int msm_pm_sleep_mode_allow(unsigned int cpu, unsigned int mode, bool idle);
/**
* msm_pm_sleep_mode_supported() - API to determine if sleep mode is
* supported.
* @cpu: CPU on which to check for the sleep mode.
* @mode: Sleep Mode to check for.
* @idle: Idle or Suspend Sleep Mode.
*
* Helper function to determine if a Idle or Suspend
* Sleep mode is allowed and enabled for a specific CPU.
*
* Return: 1 for supported; 0 if not supported.
*/
int msm_pm_sleep_mode_supported(unsigned int cpu, unsigned int mode, bool idle);
struct msm_pm_cpr_ops {
void (*cpr_suspend)(void);
void (*cpr_resume)(void);
};
void __init msm_pm_set_tz_retention_flag(unsigned int flag);
void msm_pm_enable_retention(bool enable);
bool msm_pm_retention_enabled(void);
bool msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode, bool from_idle);
static inline void msm_arch_idle(void)
{
mb(); /* Flush */
wfi();
}
#ifdef CONFIG_MSM_PM
void msm_pm_set_rpm_wakeup_irq(unsigned int irq);
int msm_pm_wait_cpu_shutdown(unsigned int cpu);
int __init msm_pm_sleep_status_init(void);
void lpm_cpu_hotplug_enter(unsigned int cpu);
s32 msm_cpuidle_get_deep_idle_latency(void);
int msm_pm_collapse(unsigned long unused);
/**
* lpm_get_latency() - API to get latency for a low power mode
* @latency_level: pointer to structure with below elements
* affinity_level: The level (CPU/L2/CCI etc.) for which the
* latency is required.
* LPM_AFF_LVL_CPU : CPU level
* LPM_AFF_LVL_L2 : L2 level
* LPM_AFF_LVL_CCI : CCI level
* reset_level: Can be passed "LPM_RESET_LVL_GDHS" for
* low power mode with control logic power collapse or
* "LPM_RESET_LVL_PC" for low power mode with control and
* memory logic power collapse or "LPM_RESET_LVL_RET" for
* retention mode.
* level_name: Pointer to the cluster name for which the latency
* is required or NULL if the minimum value out of all the
* clusters is to be returned. For CPU level, the name of the
* L2 cluster to be passed. For CCI it has no effect.
* @latency: address to get the latency value.
*
* latency value will be for the particular cluster or the minimum
* value out of all the clusters at the particular affinity_level
* and reset_level.
*
* Return: 0 for success; Error number for failure.
*/
int lpm_get_latency(struct latency_level *level, uint32_t *latency);
#else
static inline void msm_pm_set_rpm_wakeup_irq(unsigned int irq) {}
static inline int msm_pm_wait_cpu_shutdown(unsigned int cpu) { return 0; }
static inline int msm_pm_sleep_status_init(void) { return 0; };
static inline void lpm_cpu_hotplug_enter(unsigned int cpu)
{
msm_arch_idle();
};
static inline s32 msm_cpuidle_get_deep_idle_latency(void) { return 0; }
#define msm_pm_collapse NULL
static inline int lpm_get_latency(struct latency_level *level,
uint32_t *latency)
{
return 0;
}
#endif
#ifdef CONFIG_HOTPLUG_CPU
int msm_platform_secondary_init(unsigned int cpu);
#else
static inline int msm_platform_secondary_init(unsigned int cpu) { return 0; }
#endif
enum msm_pm_time_stats_id {
MSM_PM_STAT_REQUESTED_IDLE = 0,
MSM_PM_STAT_IDLE_SPIN,
MSM_PM_STAT_IDLE_WFI,
MSM_PM_STAT_RETENTION,
MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE,
MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE,
MSM_PM_STAT_IDLE_POWER_COLLAPSE,
MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE,
MSM_PM_STAT_SUSPEND,
MSM_PM_STAT_FAILED_SUSPEND,
MSM_PM_STAT_NOT_IDLE,
MSM_PM_STAT_COUNT
};
#ifdef CONFIG_MSM_IDLE_STATS
void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, int size);
void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t);
void msm_pm_l2_add_stat(uint32_t id, int64_t t);
#else
static inline void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats,
int size) {}
static inline void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) {}
static inline void msm_pm_l2_add_stat(uint32_t id, int64_t t) {}
#endif
void msm_pm_set_cpr_ops(struct msm_pm_cpr_ops *ops);
extern dma_addr_t msm_pc_debug_counters_phys;
#endif /* __ARCH_ARM_MACH_MSM_PM_H */

View file

@ -0,0 +1,103 @@
/* Copyright (c) 2014,2016 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM msm_core
#if !defined(_TRACE_MSM_CORE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_MSM_CORE_H
#include <linux/tracepoint.h>
#include <linux/thermal.h>
TRACE_EVENT(cpu_stats,
TP_PROTO(unsigned int cpu, long temp,
uint64_t min_power, uint64_t max_power),
TP_ARGS(cpu, temp, min_power, max_power),
TP_STRUCT__entry(
__field(unsigned int, cpu)
__field(long, temp)
__field(uint64_t, min_power)
__field(uint64_t, max_power)
),
TP_fast_assign(
__entry->cpu = cpu;
__entry->temp = temp;
__entry->min_power = min_power;
__entry->max_power = max_power;
),
TP_printk("Cpu%d: temp:%ld power@minfreq:%llu power@maxfreq:%llu",
__entry->cpu, __entry->temp, __entry->min_power,
__entry->max_power)
);
TRACE_EVENT(temp_threshold,
TP_PROTO(unsigned int cpu, long temp,
long hi_thresh, long low_thresh),
TP_ARGS(cpu, temp, hi_thresh, low_thresh),
TP_STRUCT__entry(
__field(unsigned int, cpu)
__field(long, temp)
__field(long, hi_thresh)
__field(long, low_thresh)
),
TP_fast_assign(
__entry->cpu = cpu;
__entry->temp = temp;
__entry->hi_thresh = hi_thresh;
__entry->low_thresh = low_thresh;
),
TP_printk("Cpu%d: temp:%ld hi_thresh:%ld low_thresh:%ld",
__entry->cpu, __entry->temp, __entry->hi_thresh,
__entry->low_thresh)
);
TRACE_EVENT(temp_notification,
TP_PROTO(unsigned int sensor_id, enum thermal_trip_type type,
int temp, int prev_temp),
TP_ARGS(sensor_id, type, temp, prev_temp),
TP_STRUCT__entry(
__field(unsigned int, sensor_id)
__field(enum thermal_trip_type, type)
__field(int, temp)
__field(int, prev_temp)
),
TP_fast_assign(
__entry->sensor_id = sensor_id;
__entry->type = type;
__entry->temp = temp;
__entry->prev_temp = prev_temp;
),
TP_printk("Sensor_id%d: %s threshold triggered temp:%d(previous:%d)",
__entry->sensor_id,
__entry->type == THERMAL_TRIP_CONFIGURABLE_HI ? "High" : "Low",
__entry->temp, __entry->prev_temp)
);
#endif
#define TRACE_INCLUDE_FILE trace_msm_core
#include <trace/define_trace.h>

View file

@ -0,0 +1,273 @@
/* Copyright (c) 2012, 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM msm_low_power
#if !defined(_TRACE_MSM_LOW_POWER_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_MSM_LOW_POWER_H_
#include <linux/tracepoint.h>
TRACE_EVENT(cpu_power_select,
TP_PROTO(int index, u32 sleep_us, u32 latency, u32 next_event_us),
TP_ARGS(index, sleep_us, latency, next_event_us),
TP_STRUCT__entry(
__field(int, index)
__field(u32, sleep_us)
__field(u32, latency)
__field(u32, next_event_us)
),
TP_fast_assign(
__entry->index = index;
__entry->sleep_us = sleep_us;
__entry->latency = latency;
__entry->next_event_us = next_event_us;
),
TP_printk("idx:%d sleep_time:%u latency:%u next_event:%u",
__entry->index, __entry->sleep_us, __entry->latency,
__entry->next_event_us)
);
TRACE_EVENT(cpu_pred_select,
TP_PROTO(u32 predtype, u64 predicted, u32 tmr_time),
TP_ARGS(predtype, predicted, tmr_time),
TP_STRUCT__entry(
__field(u32, predtype)
__field(u64, predicted)
__field(u32, tmr_time)
),
TP_fast_assign(
__entry->predtype = predtype;
__entry->predicted = predicted;
__entry->tmr_time = tmr_time;
),
TP_printk("pred:%u time:%lu tmr_time:%u",
__entry->predtype, (unsigned long)__entry->predicted,
__entry->tmr_time)
);
TRACE_EVENT(cpu_pred_hist,
TP_PROTO(int idx, u32 resi, u32 sample, u32 tmr),
TP_ARGS(idx, resi, sample, tmr),
TP_STRUCT__entry(
__field(int, idx)
__field(u32, resi)
__field(u32, sample)
__field(u32, tmr)
),
TP_fast_assign(
__entry->idx = idx;
__entry->resi = resi;
__entry->sample = sample;
__entry->tmr = tmr;
),
TP_printk("idx:%d resi:%u sample:%u tmr:%u",
__entry->idx, __entry->resi,
__entry->sample, __entry->tmr)
);
TRACE_EVENT(cpu_idle_enter,
TP_PROTO(int index),
TP_ARGS(index),
TP_STRUCT__entry(
__field(int, index)
),
TP_fast_assign(
__entry->index = index;
),
TP_printk("idx:%d",
__entry->index)
);
TRACE_EVENT(cpu_idle_exit,
TP_PROTO(int index, bool success),
TP_ARGS(index, success),
TP_STRUCT__entry(
__field(int, index)
__field(bool, success)
),
TP_fast_assign(
__entry->index = index;
__entry->success = success;
),
TP_printk("idx:%d success:%d",
__entry->index,
__entry->success)
);
TRACE_EVENT(cluster_enter,
TP_PROTO(const char *name, int index, unsigned long sync_cpus,
unsigned long child_cpus, bool from_idle),
TP_ARGS(name, index, sync_cpus, child_cpus, from_idle),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, index)
__field(unsigned long, sync_cpus)
__field(unsigned long, child_cpus)
__field(bool, from_idle)
),
TP_fast_assign(
__entry->name = name;
__entry->index = index;
__entry->sync_cpus = sync_cpus;
__entry->child_cpus = child_cpus;
__entry->from_idle = from_idle;
),
TP_printk("cluster_name:%s idx:%d sync:0x%lx child:0x%lx idle:%d",
__entry->name,
__entry->index,
__entry->sync_cpus,
__entry->child_cpus,
__entry->from_idle)
);
TRACE_EVENT(cluster_exit,
TP_PROTO(const char *name, int index, unsigned long sync_cpus,
unsigned long child_cpus, bool from_idle),
TP_ARGS(name, index, sync_cpus, child_cpus, from_idle),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, index)
__field(unsigned long, sync_cpus)
__field(unsigned long, child_cpus)
__field(bool, from_idle)
),
TP_fast_assign(
__entry->name = name;
__entry->index = index;
__entry->sync_cpus = sync_cpus;
__entry->child_cpus = child_cpus;
__entry->from_idle = from_idle;
),
TP_printk("cluster_name:%s idx:%d sync:0x%lx child:0x%lx idle:%d",
__entry->name,
__entry->index,
__entry->sync_cpus,
__entry->child_cpus,
__entry->from_idle)
);
TRACE_EVENT(cluster_pred_select,
TP_PROTO(const char *name, int index, u32 sleep_us,
u32 latency, int pred, u32 pred_us),
TP_ARGS(name, index, sleep_us, latency, pred, pred_us),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, index)
__field(u32, sleep_us)
__field(u32, latency)
__field(int, pred)
__field(u32, pred_us)
),
TP_fast_assign(
__entry->name = name;
__entry->index = index;
__entry->sleep_us = sleep_us;
__entry->latency = latency;
__entry->pred = pred;
__entry->pred_us = pred_us;
),
TP_printk("name:%s idx:%d sleep_time:%u latency:%u pred:%d pred_us:%u",
__entry->name, __entry->index, __entry->sleep_us,
__entry->latency, __entry->pred, __entry->pred_us)
);
TRACE_EVENT(cluster_pred_hist,
TP_PROTO(const char *name, int idx, u32 resi,
u32 sample, u32 tmr),
TP_ARGS(name, idx, resi, sample, tmr),
TP_STRUCT__entry(
__field(const char *, name)
__field(int, idx)
__field(u32, resi)
__field(u32, sample)
__field(u32, tmr)
),
TP_fast_assign(
__entry->name = name;
__entry->idx = idx;
__entry->resi = resi;
__entry->sample = sample;
__entry->tmr = tmr;
),
TP_printk("name:%s idx:%d resi:%u sample:%u tmr:%u",
__entry->name, __entry->idx, __entry->resi,
__entry->sample, __entry->tmr)
);
TRACE_EVENT(pre_pc_cb,
TP_PROTO(int tzflag),
TP_ARGS(tzflag),
TP_STRUCT__entry(
__field(int, tzflag)
),
TP_fast_assign(
__entry->tzflag = tzflag;
),
TP_printk("tzflag:%d",
__entry->tzflag
)
);
#endif
#define TRACE_INCLUDE_FILE trace_msm_low_power
#include <trace/define_trace.h>

View file

@ -508,3 +508,4 @@ header-y += userfaultfd.h
header-y += ipa_qmi_service_v01.h
header-y += msm_ipa.h
header-y += rmnet_ipa_fd_ioctl.h
header-y += msm-core-interface.h

View file

@ -0,0 +1,29 @@
#ifndef __MSM_CORE_LIB_H__
#define __MSM_CORE_LIB_H__
#include <linux/ioctl.h>
#define TEMP_DATA_POINTS 13
#define MAX_NUM_FREQ 200
enum msm_core_ioctl_params {
MSM_CORE_LEAKAGE,
MSM_CORE_VOLTAGE,
};
#define MSM_CORE_MAGIC 0x9D
struct sched_params {
uint32_t cpumask;
uint32_t cluster;
uint32_t power[TEMP_DATA_POINTS][MAX_NUM_FREQ];
uint32_t voltage[MAX_NUM_FREQ];
uint32_t freq[MAX_NUM_FREQ];
};
#define EA_LEAKAGE _IOWR(MSM_CORE_MAGIC, MSM_CORE_LEAKAGE,\
struct sched_params)
#define EA_VOLT _IOWR(MSM_CORE_MAGIC, MSM_CORE_VOLTAGE,\
struct sched_params)
#endif