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:
parent
943c6703b4
commit
c2ea76f6d3
20 changed files with 7292 additions and 0 deletions
324
Documentation/devicetree/bindings/arm/msm/lpm-levels.txt
Normal file
324
Documentation/devicetree/bindings/arm/msm/lpm-levels.txt
Normal 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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
71
Documentation/devicetree/bindings/arm/msm/msm_core.txt
Normal file
71
Documentation/devicetree/bindings/arm/msm/msm_core.txt
Normal 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>;
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
|
|||
1058
drivers/cpuidle/lpm-levels-of.c
Normal file
1058
drivers/cpuidle/lpm-levels-of.c
Normal file
File diff suppressed because it is too large
Load diff
1967
drivers/cpuidle/lpm-levels.c
Normal file
1967
drivers/cpuidle/lpm-levels.c
Normal file
File diff suppressed because it is too large
Load diff
166
drivers/cpuidle/lpm-levels.h
Normal file
166
drivers/cpuidle/lpm-levels.h
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
330
drivers/soc/qcom/debug_core.c
Normal file
330
drivers/soc/qcom/debug_core.c
Normal 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);
|
||||
503
drivers/soc/qcom/event_timer.c
Normal file
503
drivers/soc/qcom/event_timer.c
Normal 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;
|
||||
}
|
||||
864
drivers/soc/qcom/lpm-stats.c
Normal file
864
drivers/soc/qcom/lpm-stats.c
Normal 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
1129
drivers/soc/qcom/msm-core.c
Normal file
File diff suppressed because it is too large
Load diff
80
include/soc/qcom/event_timer.h
Normal file
80
include/soc/qcom/event_timer.h
Normal 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 */
|
||||
83
include/soc/qcom/lpm-stats.h
Normal file
83
include/soc/qcom/lpm-stats.h
Normal 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 */
|
||||
24
include/soc/qcom/msm-core.h
Normal file
24
include/soc/qcom/msm-core.h
Normal 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
214
include/soc/qcom/pm.h
Normal 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 */
|
||||
103
include/trace/events/trace_msm_core.h
Normal file
103
include/trace/events/trace_msm_core.h
Normal 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>
|
||||
273
include/trace/events/trace_msm_low_power.h
Normal file
273
include/trace/events/trace_msm_low_power.h
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
29
include/uapi/linux/msm-core-interface.h
Normal file
29
include/uapi/linux/msm-core-interface.h
Normal 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
|
||||
Loading…
Reference in a new issue