soc: spm: Snapshot of the SPM driver from 3.18 kernel
This is a snapshot of the SPM driver from 3.18 kernel. The upstream spm.c file is used as a idle driver. So updated spm driver from 3.18 kernel to msm-spm.c on 4.4 kernel. Change-Id: I73b020214fdcc7eb695cf8f5b52cf7885a0a10cd Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
This commit is contained in:
parent
f9157b4ed2
commit
de64ab2e86
8 changed files with 1981 additions and 1 deletions
169
Documentation/devicetree/bindings/arm/msm/msm-spm.txt
Normal file
169
Documentation/devicetree/bindings/arm/msm/msm-spm.txt
Normal file
|
@ -0,0 +1,169 @@
|
|||
* MSM Subsystem Power Manager (spm-v2)
|
||||
|
||||
S4 generation of MSMs have SPM hardware blocks to control the Application
|
||||
Processor Sub-System power. These SPM blocks run individual state machine
|
||||
to determine what the core (L2 or Krait/Scorpion) would do when the WFI
|
||||
instruction is executed by the core. The SAW hardware block handles SPM and
|
||||
AVS functionality for the cores.
|
||||
|
||||
The devicetree representation of the SPM block should be:
|
||||
|
||||
Required properties
|
||||
|
||||
- compatible: "qcom,spm-v2"
|
||||
- reg: The physical address and the size of the SPM's memory mapped registers
|
||||
- qcom,cpu: phandle for the CPU that the SPM block is attached to. This field
|
||||
is required on only for SPMs that control the CPU. This field is not required
|
||||
for SPMs that control L2/CCI/L3
|
||||
- qcom,saw2-ver-reg: The location of the version register
|
||||
- qcom,name: The name with which a SPM device is identified by the power
|
||||
management code.
|
||||
|
||||
----------------------------------------------------
|
||||
Non-PSCI targets should follow the rules shown below
|
||||
----------------------------------------------------
|
||||
Required properties for only Non-PSCI targets:
|
||||
|
||||
- qcom,saw2-cfg: SAW2 configuration register
|
||||
- qcom,saw2-spm-ctl: The SPM control register
|
||||
- qcom,saw2-spm-dly: Provides the values for the SPM delay command in the SPM
|
||||
sequence
|
||||
|
||||
Optional properties for only Non-PSCI targets
|
||||
- reg-names: Register names for the physical address required if spm device
|
||||
has more than one physical addressed to be mapped. Allowed register
|
||||
names are: "saw-base", "q2s", "hw-flush", "slpreq"
|
||||
- qcom,saw2-avs-ctl: The AVS control register
|
||||
- qcom,saw2-avs-hysterisis: The AVS hysterisis register to delay the AVS
|
||||
controller requests
|
||||
- qcom,vctl-timeout-us: The timeout value in us to wait for voltage to change
|
||||
after sending the voltage command to the PMIC
|
||||
- qcom,saw2-avs-limit: The AVS limit register
|
||||
- qcom,saw2-avs-dly: The AVS delay register is used to specify the delay values
|
||||
between AVS controller requests
|
||||
- qcom,saw2-pmic-data0..7: Specify the pmic data value and the associated FTS
|
||||
index to send the PMIC data to
|
||||
- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
|
||||
voltage
|
||||
- qcom,phase-port: The PVC port used for changing the number of phases
|
||||
- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
|
||||
- qcom,cpu-vctl-mask: Mask of cpus, whose voltage the spm device can control.
|
||||
Depricated: Replaced with cpu-vctl-list when cpu phandles are available.
|
||||
- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
|
||||
can control.
|
||||
- qcom,use-qchannel-for-pc: Boolean property to specify if qchannel should be
|
||||
ignored when entering power collapse. If this property is set qchannel
|
||||
will not be ignored in power collapse.
|
||||
- qcom,supports-rpm-hs: Indicates that this SPM instance allow handshake with
|
||||
RPM processor when executing the sleep command in the SPM sequence. Supported
|
||||
only on SAW2 v3.0 and above.
|
||||
- qcom,use-spm-clock-gating: This boolean property is used to indicate that
|
||||
the SPM needs to be used for clock gating. Using the SPM for clock
|
||||
gating would result in auto clock gating being disabled. Use this on
|
||||
targets that do not support or do not use auto clock gating.
|
||||
- qcom,use-qchannel-for-wfi: This boolean property is used to indicate
|
||||
that the SPM gets triggerd by the qchannel and not by means of
|
||||
wfi. So a wfe could trigger a spm for clock gating as well.
|
||||
- modes: Lists all the available low power modes for the device
|
||||
|
||||
Second level properties for modes
|
||||
|
||||
Required properties (if modes node is available)
|
||||
- qcom,label: Specifies the mode name such as:
|
||||
qcom,saw2-spm-cmd-wfi: WFI mode
|
||||
qcom,saw2-spm-cmd-ret: Retention mode
|
||||
qcom,saw2-spm-cmd-spc: Standalone PC mode
|
||||
qcom,saw2-spm-cmd-pc: Power Collapse mode
|
||||
qcom,saw2-spm-cmd-gdhs: GDHS mode
|
||||
- qcom,sequence: Specifies sequence for the low power mode
|
||||
Optional properties
|
||||
- qcom,pc_mode: Specifies pc_mode bit should be set in the SPM control register
|
||||
- qcom,ret_mode: Specifies ret_mode bit should be set in the SPM control register
|
||||
- qcom,spm_en: Specifies spm_en bit should be set in the SPM control register
|
||||
- qcom,isar: Specifies isar bit should be set in the SPM control register
|
||||
Specify this property only if SPM should retain its start address at
|
||||
the end of the program.
|
||||
- qcom,slp_cmd_mode: Specifies slp_cmd_mode bit should be set in SPM control register.
|
||||
Adding this property results in SPM handshaking with RPM. Please remove
|
||||
the RPM handshake command from the sleep sequence, replace that with
|
||||
Sleep without RPM handshake command.
|
||||
- qcom,event_sync: Specifies event_sync byte should be set in SPM control
|
||||
register.
|
||||
|
||||
----------------------------------------------------
|
||||
PSCI targets should follow the rules shown below
|
||||
----------------------------------------------------
|
||||
Optional properties for only PSCI targets:
|
||||
|
||||
- qcom,saw2-avs-ctl: The AVS control register
|
||||
- qcom,saw2-avs-hysterisis: The AVS hysterisis register to delay the AVS
|
||||
controller requests
|
||||
- qcom,vctl-timeout-us: The timeout value in us to wait for voltage to change
|
||||
after sending the voltage command to the PMIC
|
||||
- qcom,saw2-avs-limit: The AVS limit register
|
||||
- qcom,saw2-avs-dly: The AVS delay register is used to specify the delay values
|
||||
between AVS controller requests
|
||||
- qcom,vctl-port: The PVC (PMIC Virtual Channel) port used for changing
|
||||
voltage
|
||||
- qcom,phase-port: The PVC port used for changing the number of phases
|
||||
- qcom,pfm-port: The PVC port used for enabling PWM/PFM modes
|
||||
- qcom,cpu-vctl-list: List of cpu node phandles, whose voltage the spm device
|
||||
can control.
|
||||
|
||||
|
||||
Example 1:
|
||||
qcom,spm@f9089000 {
|
||||
compatible = "qcom,spm-v2";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
reg = <0xf9089000 0x1000>;
|
||||
qcom,cpu = <&CPU0>;
|
||||
qcom,saw2-ver-reg = <0xfd0>;
|
||||
qcom,saw2-cfg = <0x1b>;
|
||||
qcom,saw2-avs-ctl = <0>;
|
||||
qcom,saw2-avs-hysteresis = <0>;
|
||||
qcom,saw2-avs-limit = <0>;
|
||||
qcom,saw2-avs-dly= <0>;
|
||||
qcom,saw2-spm-dly= <0x20000400>;
|
||||
qcom,saw2-spm-ctl = <0x1>;
|
||||
qcom,cpu-vctl-list = <&CPU0 &CPU1 &CPU2 &CPU3>;
|
||||
qcom,mode0 {
|
||||
qcom,label = "qcom,saw2-spm-cmd-wfi";
|
||||
qcom,sequence = [03 0b 0f];
|
||||
qcom,spm_en;
|
||||
};
|
||||
|
||||
qcom,mode1 {
|
||||
qcom,label = "qcom,saw2-spm-cmd-spc";
|
||||
qcom,sequence = [00 20 50 80 60 70 10 92
|
||||
a0 b0 03 68 70 3b 92 a0 b0
|
||||
82 2b 50 10 30 02 22 30 0f];
|
||||
qcom,spm_en;
|
||||
qcom,pc_mode;
|
||||
};
|
||||
|
||||
qcom,mode2 {
|
||||
qcom,label = "qcom,saw2-spm-cmd-pc";
|
||||
qcom,sequence = [00 20 10 92 a0 b0 07 3b 92
|
||||
a0 b0 82 10 30 02 22 30 0f];
|
||||
qcom,spm_en;
|
||||
qcom,pc_mode;
|
||||
};
|
||||
};
|
||||
|
||||
Example 2:
|
||||
qcom,spm@9A10000 {
|
||||
compatible = "qcom,spm-v2";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
reg = <0x9A10000 0x1000>;
|
||||
qcom,name = "system-cbf"; /* CBF SAW */
|
||||
qcom,saw2-ver-reg = <0xFD0>;
|
||||
qcom,cpu-vctl-list = <&CPU0 &CPU1 &CPU2 &CPU3>;
|
||||
qcom,vctl-timeout-us = <50>;
|
||||
qcom,vctl-port = <0x0>;
|
||||
qcom,phase-port = <0x1>;
|
||||
qcom,saw2-avs-ctl = <0x1100>;
|
||||
qcom,pfm-port = <0x2>;
|
||||
};
|
||||
|
|
@ -156,6 +156,7 @@ CONFIG_MSM_GLINK_LOOPBACK_SERVER=y
|
|||
CONFIG_MSM_GLINK_SMD_XPRT=y
|
||||
CONFIG_MSM_GLINK_SMEM_NATIVE_XPRT=y
|
||||
CONFIG_MSM_RPM_SMD=y
|
||||
CONFIG_MSM_SPM=y
|
||||
CONFIG_QCOM_SCM_XPU=y
|
||||
CONFIG_QCOM_SCM=y
|
||||
CONFIG_QCOM_WATCHDOG_V2=y
|
||||
|
|
|
@ -128,6 +128,14 @@ config QCOM_SMD_RPM
|
|||
Say M here if you want to include support for the Qualcomm RPM as a
|
||||
module. This will build a module called "qcom-smd-rpm".
|
||||
|
||||
config MSM_SPM
|
||||
bool "Driver support for SPM and AVS wrapper hardware"
|
||||
help
|
||||
Enables the support SAW and AVS wrapper hardware on MSMs SPM
|
||||
hardware is used to manage the processor power during sleep. The
|
||||
driver allows configuring SPM to allow different low power modes for
|
||||
both core and L2.
|
||||
|
||||
config QCOM_SCM
|
||||
bool "Secure Channel Manager (SCM) support"
|
||||
default n
|
||||
|
|
|
@ -13,6 +13,7 @@ obj-$(CONFIG_QCOM_PM) += spm.o
|
|||
obj-$(CONFIG_QCOM_SMD) += smd.o
|
||||
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
|
||||
obj-$(CONFIG_QCOM_SMEM) += smem.o
|
||||
obj-$(CONFIG_MSM_SPM) += msm-spm.o spm_devices.o
|
||||
CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
|
||||
|
||||
obj-$(CONFIG_QCOM_SCM_ERRATA) += scm-errata.o
|
||||
|
|
682
drivers/soc/qcom/msm-spm.c
Normal file
682
drivers/soc/qcom/msm-spm.c
Normal file
|
@ -0,0 +1,682 @@
|
|||
/* Copyright (c) 2011-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/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "spm_driver.h"
|
||||
|
||||
#define MSM_SPM_PMIC_STATE_IDLE 0
|
||||
|
||||
enum {
|
||||
MSM_SPM_DEBUG_SHADOW = 1U << 0,
|
||||
MSM_SPM_DEBUG_VCTL = 1U << 1,
|
||||
};
|
||||
|
||||
static int msm_spm_debug_mask;
|
||||
module_param_named(
|
||||
debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP
|
||||
);
|
||||
|
||||
struct saw2_data {
|
||||
const char *ver_name;
|
||||
uint32_t major;
|
||||
uint32_t minor;
|
||||
uint32_t *spm_reg_offset_ptr;
|
||||
};
|
||||
|
||||
static uint32_t msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
|
||||
[MSM_SPM_REG_SAW_SECURE] = 0x00,
|
||||
[MSM_SPM_REG_SAW_ID] = 0x04,
|
||||
[MSM_SPM_REG_SAW_CFG] = 0x08,
|
||||
[MSM_SPM_REG_SAW_SPM_STS] = 0x0C,
|
||||
[MSM_SPM_REG_SAW_AVS_STS] = 0x10,
|
||||
[MSM_SPM_REG_SAW_PMIC_STS] = 0x14,
|
||||
[MSM_SPM_REG_SAW_RST] = 0x18,
|
||||
[MSM_SPM_REG_SAW_VCTL] = 0x1C,
|
||||
[MSM_SPM_REG_SAW_AVS_CTL] = 0x20,
|
||||
[MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24,
|
||||
[MSM_SPM_REG_SAW_AVS_DLY] = 0x28,
|
||||
[MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C,
|
||||
[MSM_SPM_REG_SAW_SPM_CTL] = 0x30,
|
||||
[MSM_SPM_REG_SAW_SPM_DLY] = 0x34,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C,
|
||||
[MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x80,
|
||||
[MSM_SPM_REG_SAW_VERSION] = 0xFD0,
|
||||
};
|
||||
|
||||
static uint32_t msm_spm_reg_offsets_saw2_v3_0[MSM_SPM_REG_NR] = {
|
||||
[MSM_SPM_REG_SAW_SECURE] = 0x00,
|
||||
[MSM_SPM_REG_SAW_ID] = 0x04,
|
||||
[MSM_SPM_REG_SAW_CFG] = 0x08,
|
||||
[MSM_SPM_REG_SAW_SPM_STS] = 0x0C,
|
||||
[MSM_SPM_REG_SAW_AVS_STS] = 0x10,
|
||||
[MSM_SPM_REG_SAW_PMIC_STS] = 0x14,
|
||||
[MSM_SPM_REG_SAW_RST] = 0x18,
|
||||
[MSM_SPM_REG_SAW_VCTL] = 0x1C,
|
||||
[MSM_SPM_REG_SAW_AVS_CTL] = 0x20,
|
||||
[MSM_SPM_REG_SAW_AVS_LIMIT] = 0x24,
|
||||
[MSM_SPM_REG_SAW_AVS_DLY] = 0x28,
|
||||
[MSM_SPM_REG_SAW_AVS_HYSTERESIS] = 0x2C,
|
||||
[MSM_SPM_REG_SAW_SPM_CTL] = 0x30,
|
||||
[MSM_SPM_REG_SAW_SPM_DLY] = 0x34,
|
||||
[MSM_SPM_REG_SAW_STS2] = 0x38,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_0] = 0x40,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_1] = 0x44,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_2] = 0x48,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_3] = 0x4C,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_4] = 0x50,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_5] = 0x54,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_6] = 0x58,
|
||||
[MSM_SPM_REG_SAW_PMIC_DATA_7] = 0x5C,
|
||||
[MSM_SPM_REG_SAW_SEQ_ENTRY] = 0x400,
|
||||
[MSM_SPM_REG_SAW_VERSION] = 0xFD0,
|
||||
};
|
||||
|
||||
static struct saw2_data saw2_info[] = {
|
||||
[0] = {
|
||||
"SAW_v2.1",
|
||||
0x2,
|
||||
0x1,
|
||||
msm_spm_reg_offsets_saw2_v2_1,
|
||||
},
|
||||
[1] = {
|
||||
"SAW_v2.3",
|
||||
0x3,
|
||||
0x0,
|
||||
msm_spm_reg_offsets_saw2_v3_0,
|
||||
},
|
||||
[2] = {
|
||||
"SAW_v3.0",
|
||||
0x1,
|
||||
0x0,
|
||||
msm_spm_reg_offsets_saw2_v3_0,
|
||||
},
|
||||
};
|
||||
|
||||
static uint32_t num_pmic_data;
|
||||
|
||||
static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev,
|
||||
unsigned int reg_index)
|
||||
{
|
||||
BUG_ON(!dev);
|
||||
|
||||
BUG_ON(!dev->reg_shadow);
|
||||
|
||||
__raw_writel(dev->reg_shadow[reg_index],
|
||||
dev->reg_base_addr + dev->reg_offsets[reg_index]);
|
||||
}
|
||||
|
||||
static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev,
|
||||
unsigned int reg_index)
|
||||
{
|
||||
dev->reg_shadow[reg_index] =
|
||||
__raw_readl(dev->reg_base_addr +
|
||||
dev->reg_offsets[reg_index]);
|
||||
}
|
||||
|
||||
static inline uint32_t msm_spm_drv_get_num_spm_entry(
|
||||
struct msm_spm_driver_data *dev)
|
||||
{
|
||||
BUG_ON(!dev);
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
|
||||
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
static inline void msm_spm_drv_set_start_addr(
|
||||
struct msm_spm_driver_data *dev, uint32_t ctl)
|
||||
{
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] = ctl;
|
||||
}
|
||||
|
||||
static inline bool msm_spm_pmic_arb_present(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
|
||||
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 2) & 0x1;
|
||||
}
|
||||
|
||||
static inline void msm_spm_drv_set_vctl2(struct msm_spm_driver_data *dev,
|
||||
uint32_t vlevel)
|
||||
{
|
||||
unsigned int pmic_data = 0;
|
||||
|
||||
/**
|
||||
* VCTL_PORT has to be 0, for PMIC_STS register to be updated.
|
||||
* Ensure that vctl_port is always set to 0.
|
||||
*/
|
||||
WARN_ON(dev->vctl_port);
|
||||
|
||||
pmic_data |= vlevel;
|
||||
pmic_data |= (dev->vctl_port & 0x7) << 16;
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data;
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] &= ~0x700FF;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_DATA_3] |= pmic_data;
|
||||
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_PMIC_DATA_3);
|
||||
}
|
||||
|
||||
static inline uint32_t msm_spm_drv_get_num_pmic_data(
|
||||
struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_ID);
|
||||
mb();
|
||||
return (dev->reg_shadow[MSM_SPM_REG_SAW_ID] >> 4) & 0x7;
|
||||
}
|
||||
|
||||
static inline uint32_t msm_spm_drv_get_sts_pmic_state(
|
||||
struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
|
||||
return (dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] >> 16) &
|
||||
0x03;
|
||||
}
|
||||
|
||||
uint32_t msm_spm_drv_get_sts_curr_pmic_data(
|
||||
struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
|
||||
return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0xFF;
|
||||
}
|
||||
|
||||
static inline void msm_spm_drv_get_saw2_ver(struct msm_spm_driver_data *dev,
|
||||
uint32_t *major, uint32_t *minor)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_VERSION] =
|
||||
__raw_readl(dev->reg_base_addr + dev->ver_reg);
|
||||
|
||||
val = dev->reg_shadow[MSM_SPM_REG_SAW_VERSION];
|
||||
|
||||
*major = (val >> 28) & 0xF;
|
||||
*minor = (val >> 16) & 0xFFF;
|
||||
}
|
||||
|
||||
inline int msm_spm_drv_set_spm_enable(
|
||||
struct msm_spm_driver_data *dev, bool enable)
|
||||
{
|
||||
uint32_t value = enable ? 0x01 : 0x00;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
if ((dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] & 0x01) ^ value) {
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x1;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= value;
|
||||
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
|
||||
wmb();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_spm_drv_get_avs_enable(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x01;
|
||||
}
|
||||
|
||||
int msm_spm_drv_set_avs_enable(struct msm_spm_driver_data *dev,
|
||||
bool enable)
|
||||
{
|
||||
uint32_t value = enable ? 0x1 : 0x0;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & 0x1) ^ value) {
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x1;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value;
|
||||
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_spm_drv_set_avs_limit(struct msm_spm_driver_data *dev,
|
||||
uint32_t min_lvl, uint32_t max_lvl)
|
||||
{
|
||||
uint32_t value = (max_lvl & 0xff) << 16 | (min_lvl & 0xff);
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_LIMIT] = value;
|
||||
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_LIMIT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_spm_drv_avs_irq_mask(enum msm_spm_avs_irq irq)
|
||||
{
|
||||
switch (irq) {
|
||||
case MSM_SPM_AVS_IRQ_MIN:
|
||||
return BIT(1);
|
||||
case MSM_SPM_AVS_IRQ_MAX:
|
||||
return BIT(2);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int msm_spm_drv_set_avs_irq_enable(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_avs_irq irq, bool enable)
|
||||
{
|
||||
int mask = msm_spm_drv_avs_irq_mask(irq);
|
||||
uint32_t value;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
else if (mask < 0)
|
||||
return mask;
|
||||
|
||||
value = enable ? mask : 0;
|
||||
|
||||
if ((dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) ^ value) {
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= value;
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_spm_drv_avs_clear_irq(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_avs_irq irq)
|
||||
{
|
||||
int mask = msm_spm_drv_avs_irq_mask(irq);
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
else if (mask < 0)
|
||||
return mask;
|
||||
|
||||
if (dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & mask) {
|
||||
/*
|
||||
* The interrupt status is cleared by disabling and then
|
||||
* re-enabling the interrupt.
|
||||
*/
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~mask;
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= mask;
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
int i;
|
||||
int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
|
||||
|
||||
if (!dev) {
|
||||
__WARN();
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_spm_entry; i++) {
|
||||
__raw_writel(dev->reg_seq_entry_shadow[i],
|
||||
dev->reg_base_addr
|
||||
+ dev->reg_offsets[MSM_SPM_REG_SAW_SEQ_ENTRY]
|
||||
+ 4 * i);
|
||||
}
|
||||
mb();
|
||||
}
|
||||
|
||||
void dump_regs(struct msm_spm_driver_data *dev, int cpu)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS);
|
||||
mb();
|
||||
pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_STS: 0x%x\n", cpu,
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_STS]);
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
|
||||
mb();
|
||||
pr_err("CPU%d: spm register MSM_SPM_REG_SAW_SPM_CTL: 0x%x\n", cpu,
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL]);
|
||||
}
|
||||
|
||||
int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
|
||||
uint8_t *cmd, uint32_t *offset)
|
||||
{
|
||||
uint32_t cmd_w;
|
||||
uint32_t offset_w = *offset / 4;
|
||||
uint8_t last_cmd;
|
||||
|
||||
if (!cmd)
|
||||
return -EINVAL;
|
||||
|
||||
while (1) {
|
||||
int i;
|
||||
cmd_w = 0;
|
||||
last_cmd = 0;
|
||||
cmd_w = dev->reg_seq_entry_shadow[offset_w];
|
||||
|
||||
for (i = (*offset % 4); i < 4; i++) {
|
||||
last_cmd = *(cmd++);
|
||||
cmd_w |= last_cmd << (i * 8);
|
||||
(*offset)++;
|
||||
if (last_cmd == 0x0f)
|
||||
break;
|
||||
}
|
||||
|
||||
dev->reg_seq_entry_shadow[offset_w++] = cmd_w;
|
||||
if (last_cmd == 0x0f)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
|
||||
uint32_t ctl)
|
||||
{
|
||||
|
||||
/* SPM is configured to reset start address to zero after end of Program
|
||||
*/
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
msm_spm_drv_set_start_addr(dev, ctl);
|
||||
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL);
|
||||
wmb();
|
||||
|
||||
if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) {
|
||||
int i;
|
||||
for (i = 0; i < MSM_SPM_REG_NR; i++)
|
||||
pr_info("%s: reg %02x = 0x%08x\n", __func__,
|
||||
dev->reg_offsets[i], dev->reg_shadow[i]);
|
||||
}
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_SPM_STS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t msm_spm_drv_get_vdd(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_PMIC_STS);
|
||||
return dev->reg_shadow[MSM_SPM_REG_SAW_PMIC_STS] & 0xFF;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MSM_AVS_HW
|
||||
static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
return dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] & BIT(0);
|
||||
}
|
||||
|
||||
static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~BIT(27);
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= BIT(27);
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev,
|
||||
unsigned int vlevel)
|
||||
{
|
||||
vlevel &= 0x3f;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] &= ~0x7efc00;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= ((vlevel - 4) << 10);
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_AVS_CTL] |= (vlevel << 17);
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_AVS_CTL);
|
||||
}
|
||||
|
||||
#else
|
||||
static bool msm_spm_drv_is_avs_enabled(struct msm_spm_driver_data *dev)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static void msm_spm_drv_disable_avs(struct msm_spm_driver_data *dev) { }
|
||||
|
||||
static void msm_spm_drv_enable_avs(struct msm_spm_driver_data *dev) { }
|
||||
|
||||
static void msm_spm_drv_set_avs_vlevel(struct msm_spm_driver_data *dev,
|
||||
unsigned int vlevel) { }
|
||||
#endif
|
||||
|
||||
int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel)
|
||||
{
|
||||
uint32_t timeout_us, new_level;
|
||||
bool avs_enabled;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
avs_enabled = msm_spm_drv_is_avs_enabled(dev);
|
||||
|
||||
if (!msm_spm_pmic_arb_present(dev))
|
||||
return -ENOSYS;
|
||||
|
||||
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
|
||||
pr_info("%s: requesting vlevel %#x\n", __func__, vlevel);
|
||||
|
||||
if (avs_enabled)
|
||||
msm_spm_drv_disable_avs(dev);
|
||||
|
||||
/* Kick the state machine back to idle */
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_RST] = 1;
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_RST);
|
||||
|
||||
msm_spm_drv_set_vctl2(dev, vlevel);
|
||||
|
||||
timeout_us = dev->vctl_timeout_us;
|
||||
/* Confirm the voltage we set was what hardware sent */
|
||||
do {
|
||||
new_level = msm_spm_drv_get_sts_curr_pmic_data(dev);
|
||||
if (new_level == vlevel)
|
||||
break;
|
||||
udelay(1);
|
||||
} while (--timeout_us);
|
||||
if (!timeout_us) {
|
||||
pr_info("Wrong level %#x\n", new_level);
|
||||
goto set_vdd_bail;
|
||||
}
|
||||
|
||||
if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL)
|
||||
pr_info("%s: done, remaining timeout %u us\n",
|
||||
__func__, timeout_us);
|
||||
|
||||
/* Set AVS min/max */
|
||||
if (avs_enabled) {
|
||||
msm_spm_drv_set_avs_vlevel(dev, vlevel);
|
||||
msm_spm_drv_enable_avs(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
set_vdd_bail:
|
||||
if (avs_enabled)
|
||||
msm_spm_drv_enable_avs(dev);
|
||||
|
||||
pr_err("%s: failed %#x, remaining timeout %uus, vlevel %#x\n",
|
||||
__func__, vlevel, timeout_us, new_level);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int msm_spm_drv_get_pmic_port(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_pmic_port port)
|
||||
{
|
||||
int index = -1;
|
||||
|
||||
switch (port) {
|
||||
case MSM_SPM_PMIC_VCTL_PORT:
|
||||
index = dev->vctl_port;
|
||||
break;
|
||||
case MSM_SPM_PMIC_PHASE_PORT:
|
||||
index = dev->phase_port;
|
||||
break;
|
||||
case MSM_SPM_PMIC_PFM_PORT:
|
||||
index = dev->pfm_port;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_pmic_port port, unsigned int data)
|
||||
{
|
||||
unsigned int pmic_data = 0;
|
||||
unsigned int timeout_us = 0;
|
||||
int index = 0;
|
||||
|
||||
if (!msm_spm_pmic_arb_present(dev))
|
||||
return -ENOSYS;
|
||||
|
||||
index = msm_spm_drv_get_pmic_port(dev, port);
|
||||
if (index < 0)
|
||||
return -ENODEV;
|
||||
|
||||
pmic_data |= data & 0xFF;
|
||||
pmic_data |= (index & 0x7) << 16;
|
||||
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0x700FF;
|
||||
dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= pmic_data;
|
||||
msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL);
|
||||
mb();
|
||||
|
||||
timeout_us = dev->vctl_timeout_us;
|
||||
/**
|
||||
* Confirm the pmic data set was what hardware sent by
|
||||
* checking the PMIC FSM state.
|
||||
* We cannot use the sts_pmic_data and check it against
|
||||
* the value like we do fot set_vdd, since the PMIC_STS
|
||||
* is only updated for SAW_VCTL sent with port index 0.
|
||||
*/
|
||||
do {
|
||||
if (msm_spm_drv_get_sts_pmic_state(dev) ==
|
||||
MSM_SPM_PMIC_STATE_IDLE)
|
||||
break;
|
||||
udelay(1);
|
||||
} while (--timeout_us);
|
||||
|
||||
if (!timeout_us) {
|
||||
pr_err("%s: failed, remaining timeout %u us, data %d\n",
|
||||
__func__, timeout_us, data);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_spm_drv_reinit(struct msm_spm_driver_data *dev, bool seq_write)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (seq_write)
|
||||
msm_spm_drv_flush_seq_entry(dev);
|
||||
|
||||
for (i = 0; i < MSM_SPM_REG_SAW_PMIC_DATA_0 + num_pmic_data; i++)
|
||||
msm_spm_drv_load_shadow(dev, i);
|
||||
|
||||
for (i = MSM_SPM_REG_NR_INITIALIZE + 1; i < MSM_SPM_REG_NR; i++)
|
||||
msm_spm_drv_load_shadow(dev, i);
|
||||
}
|
||||
|
||||
int msm_spm_drv_reg_init(struct msm_spm_driver_data *dev,
|
||||
struct msm_spm_platform_data *data)
|
||||
{
|
||||
int i;
|
||||
bool found = false;
|
||||
|
||||
dev->ver_reg = data->ver_reg;
|
||||
dev->reg_base_addr = data->reg_base_addr;
|
||||
msm_spm_drv_get_saw2_ver(dev, &dev->major, &dev->minor);
|
||||
for (i = 0; i < ARRAY_SIZE(saw2_info); i++)
|
||||
if (dev->major == saw2_info[i].major &&
|
||||
dev->minor == saw2_info[i].minor) {
|
||||
pr_debug("%s: Version found\n",
|
||||
saw2_info[i].ver_name);
|
||||
dev->reg_offsets = saw2_info[i].spm_reg_offset_ptr;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
pr_err("%s: No SAW version found\n", __func__);
|
||||
BUG_ON(!found);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_spm_drv_upd_reg_shadow(struct msm_spm_driver_data *dev, int id,
|
||||
int val)
|
||||
{
|
||||
dev->reg_shadow[id] = val;
|
||||
msm_spm_drv_flush_shadow(dev, id);
|
||||
/* Complete the above writes before other accesses */
|
||||
mb();
|
||||
}
|
||||
|
||||
int msm_spm_drv_init(struct msm_spm_driver_data *dev,
|
||||
struct msm_spm_platform_data *data)
|
||||
{
|
||||
int num_spm_entry;
|
||||
|
||||
BUG_ON(!dev || !data);
|
||||
|
||||
dev->vctl_port = data->vctl_port;
|
||||
dev->phase_port = data->phase_port;
|
||||
dev->pfm_port = data->pfm_port;
|
||||
dev->reg_base_addr = data->reg_base_addr;
|
||||
memcpy(dev->reg_shadow, data->reg_init_values,
|
||||
sizeof(data->reg_init_values));
|
||||
|
||||
dev->vctl_timeout_us = data->vctl_timeout_us;
|
||||
|
||||
|
||||
if (!num_pmic_data)
|
||||
num_pmic_data = msm_spm_drv_get_num_pmic_data(dev);
|
||||
|
||||
num_spm_entry = msm_spm_drv_get_num_spm_entry(dev);
|
||||
|
||||
dev->reg_seq_entry_shadow =
|
||||
kzalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry,
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!dev->reg_seq_entry_shadow)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
985
drivers/soc/qcom/spm_devices.c
Normal file
985
drivers/soc/qcom/spm_devices.c
Normal file
|
@ -0,0 +1,985 @@
|
|||
/* Copyright (c) 2011-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/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <soc/qcom/spm.h>
|
||||
#include "spm_driver.h"
|
||||
|
||||
#define VDD_DEFAULT 0xDEADF00D
|
||||
#define SLP_CMD_BIT 17
|
||||
#define PC_MODE_BIT 16
|
||||
#define RET_MODE_BIT 15
|
||||
#define EVENT_SYNC_BIT 24
|
||||
#define ISAR_BIT 3
|
||||
#define SPM_EN_BIT 0
|
||||
|
||||
struct msm_spm_power_modes {
|
||||
uint32_t mode;
|
||||
uint32_t ctl;
|
||||
};
|
||||
|
||||
struct msm_spm_device {
|
||||
struct list_head list;
|
||||
bool initialized;
|
||||
const char *name;
|
||||
struct msm_spm_driver_data reg_data;
|
||||
struct msm_spm_power_modes *modes;
|
||||
uint32_t num_modes;
|
||||
uint32_t cpu_vdd;
|
||||
struct cpumask mask;
|
||||
void __iomem *q2s_reg;
|
||||
bool qchannel_ignore;
|
||||
bool allow_rpm_hs;
|
||||
bool use_spm_clk_gating;
|
||||
bool use_qchannel_for_wfi;
|
||||
void __iomem *flush_base_addr;
|
||||
void __iomem *slpreq_base_addr;
|
||||
};
|
||||
|
||||
struct msm_spm_vdd_info {
|
||||
struct msm_spm_device *vctl_dev;
|
||||
uint32_t vlevel;
|
||||
int err;
|
||||
};
|
||||
|
||||
static LIST_HEAD(spm_list);
|
||||
static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
|
||||
static DEFINE_PER_CPU(struct msm_spm_device *, cpu_vctl_device);
|
||||
|
||||
static void msm_spm_smp_set_vdd(void *data)
|
||||
{
|
||||
struct msm_spm_vdd_info *info = (struct msm_spm_vdd_info *)data;
|
||||
struct msm_spm_device *dev = info->vctl_dev;
|
||||
|
||||
dev->cpu_vdd = info->vlevel;
|
||||
info->err = msm_spm_drv_set_vdd(&dev->reg_data, info->vlevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_spm_probe_done(): Verify and return the status of the cpu(s) and l2
|
||||
* probe.
|
||||
* Return: 0 if all spm devices have been probed, else return -EPROBE_DEFER.
|
||||
* if probe failed, then return the err number for that failure.
|
||||
*/
|
||||
int msm_spm_probe_done(void)
|
||||
{
|
||||
struct msm_spm_device *dev;
|
||||
int cpu;
|
||||
int ret = 0;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
dev = per_cpu(cpu_vctl_device, cpu);
|
||||
if (!dev)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ret = IS_ERR(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_probe_done);
|
||||
|
||||
void msm_spm_dump_regs(unsigned int cpu)
|
||||
{
|
||||
dump_regs(&per_cpu(msm_cpu_spm_device, cpu).reg_data, cpu);
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_spm_set_vdd(): Set core voltage
|
||||
* @cpu: core id
|
||||
* @vlevel: Encoded PMIC data.
|
||||
*
|
||||
* Return: 0 on success or -(ERRNO) on failure.
|
||||
*/
|
||||
int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel)
|
||||
{
|
||||
struct msm_spm_vdd_info info;
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
int ret;
|
||||
|
||||
if (!dev)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ret = IS_ERR(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
info.vctl_dev = dev;
|
||||
info.vlevel = vlevel;
|
||||
|
||||
ret = smp_call_function_any(&dev->mask, msm_spm_smp_set_vdd, &info,
|
||||
true);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return info.err;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_set_vdd);
|
||||
|
||||
/**
|
||||
* msm_spm_get_vdd(): Get core voltage
|
||||
* @cpu: core id
|
||||
* @return: Returns encoded PMIC data.
|
||||
*/
|
||||
int msm_spm_get_vdd(unsigned int cpu)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
return msm_spm_drv_get_vdd(&dev->reg_data) ? : -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_get_vdd);
|
||||
|
||||
static void msm_spm_config_q2s(struct msm_spm_device *dev, unsigned int mode)
|
||||
{
|
||||
uint32_t spm_legacy_mode = 0;
|
||||
uint32_t qchannel_ignore = 0;
|
||||
uint32_t val = 0;
|
||||
|
||||
if (!dev->q2s_reg)
|
||||
return;
|
||||
|
||||
switch (mode) {
|
||||
case MSM_SPM_MODE_DISABLED:
|
||||
case MSM_SPM_MODE_CLOCK_GATING:
|
||||
qchannel_ignore = !dev->use_qchannel_for_wfi;
|
||||
spm_legacy_mode = 0;
|
||||
break;
|
||||
case MSM_SPM_MODE_RETENTION:
|
||||
qchannel_ignore = 0;
|
||||
spm_legacy_mode = 0;
|
||||
break;
|
||||
case MSM_SPM_MODE_GDHS:
|
||||
case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE:
|
||||
case MSM_SPM_MODE_POWER_COLLAPSE:
|
||||
qchannel_ignore = dev->qchannel_ignore;
|
||||
spm_legacy_mode = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
val = spm_legacy_mode << 2 | qchannel_ignore << 1;
|
||||
__raw_writel(val, dev->q2s_reg);
|
||||
mb();
|
||||
}
|
||||
|
||||
static void msm_spm_config_hw_flush(struct msm_spm_device *dev,
|
||||
unsigned int mode)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
|
||||
if (!dev->flush_base_addr)
|
||||
return;
|
||||
|
||||
switch (mode) {
|
||||
case MSM_SPM_MODE_FASTPC:
|
||||
case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE:
|
||||
case MSM_SPM_MODE_POWER_COLLAPSE:
|
||||
val = BIT(0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
__raw_writel(val, dev->flush_base_addr);
|
||||
}
|
||||
|
||||
static void msm_spm_config_slpreq(struct msm_spm_device *dev,
|
||||
unsigned int mode)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
|
||||
if (!dev->slpreq_base_addr)
|
||||
return;
|
||||
|
||||
switch (mode) {
|
||||
case MSM_SPM_MODE_FASTPC:
|
||||
case MSM_SPM_MODE_GDHS:
|
||||
case MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE:
|
||||
case MSM_SPM_MODE_POWER_COLLAPSE:
|
||||
val = BIT(4);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
val = (__raw_readl(dev->slpreq_base_addr) & ~BIT(4)) | val;
|
||||
__raw_writel(val, dev->slpreq_base_addr);
|
||||
}
|
||||
|
||||
static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev,
|
||||
unsigned int mode, bool notify_rpm)
|
||||
{
|
||||
uint32_t i;
|
||||
int ret = -EINVAL;
|
||||
uint32_t ctl;
|
||||
|
||||
if (!dev) {
|
||||
pr_err("dev is NULL\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!dev->initialized)
|
||||
return -ENXIO;
|
||||
|
||||
if (!dev->num_modes)
|
||||
return 0;
|
||||
|
||||
if (mode == MSM_SPM_MODE_DISABLED) {
|
||||
ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false);
|
||||
} else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) {
|
||||
for (i = 0; i < dev->num_modes; i++) {
|
||||
if (dev->modes[i].mode != mode)
|
||||
continue;
|
||||
|
||||
ctl = dev->modes[i].ctl;
|
||||
if (!dev->allow_rpm_hs && notify_rpm)
|
||||
ctl &= ~BIT(SLP_CMD_BIT);
|
||||
|
||||
break;
|
||||
}
|
||||
ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, ctl);
|
||||
}
|
||||
|
||||
msm_spm_config_q2s(dev, mode);
|
||||
msm_spm_config_hw_flush(dev, mode);
|
||||
msm_spm_config_slpreq(dev, mode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_spm_dev_init(struct msm_spm_device *dev,
|
||||
struct msm_spm_platform_data *data)
|
||||
{
|
||||
int i, ret = -ENOMEM;
|
||||
uint32_t offset = 0;
|
||||
|
||||
dev->cpu_vdd = VDD_DEFAULT;
|
||||
dev->num_modes = data->num_modes;
|
||||
dev->modes = kmalloc(
|
||||
sizeof(struct msm_spm_power_modes) * dev->num_modes,
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!dev->modes)
|
||||
goto spm_failed_malloc;
|
||||
|
||||
ret = msm_spm_drv_init(&dev->reg_data, data);
|
||||
|
||||
if (ret)
|
||||
goto spm_failed_init;
|
||||
|
||||
for (i = 0; i < dev->num_modes; i++) {
|
||||
|
||||
/* Default offset is 0 and gets updated as we write more
|
||||
* sequences into SPM
|
||||
*/
|
||||
dev->modes[i].ctl = data->modes[i].ctl | ((offset & 0x1FF)
|
||||
<< 4);
|
||||
ret = msm_spm_drv_write_seq_data(&dev->reg_data,
|
||||
data->modes[i].cmd, &offset);
|
||||
if (ret < 0)
|
||||
goto spm_failed_init;
|
||||
|
||||
dev->modes[i].mode = data->modes[i].mode;
|
||||
}
|
||||
|
||||
msm_spm_drv_reinit(&dev->reg_data, dev->num_modes ? true : false);
|
||||
|
||||
dev->initialized = true;
|
||||
|
||||
return 0;
|
||||
|
||||
spm_failed_init:
|
||||
kfree(dev->modes);
|
||||
spm_failed_malloc:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_spm_turn_on_cpu_rail(): Power on cpu rail before turning on core
|
||||
* @node: The SPM node that controls the voltage for the CPU
|
||||
* @val: The value to be set on the rail
|
||||
* @cpu: The cpu for this with rail is being powered on
|
||||
*/
|
||||
int msm_spm_turn_on_cpu_rail(struct device_node *vctl_node,
|
||||
unsigned int val, int cpu, int vctl_offset)
|
||||
{
|
||||
uint32_t timeout = 2000; /* delay for voltage to settle on the core */
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
void __iomem *base;
|
||||
|
||||
base = of_iomap(vctl_node, 1);
|
||||
if (base) {
|
||||
/*
|
||||
* Program Q2S to disable SPM legacy mode and ignore Q2S
|
||||
* channel requests.
|
||||
* bit[1] = qchannel_ignore = 1
|
||||
* bit[2] = spm_legacy_mode = 0
|
||||
*/
|
||||
writel_relaxed(0x2, base);
|
||||
mb();
|
||||
iounmap(base);
|
||||
}
|
||||
|
||||
base = of_iomap(vctl_node, 0);
|
||||
if (!base)
|
||||
return -ENOMEM;
|
||||
|
||||
if (dev && (dev->cpu_vdd != VDD_DEFAULT))
|
||||
return 0;
|
||||
|
||||
/* Set the CPU supply regulator voltage */
|
||||
val = (val & 0xFF);
|
||||
writel_relaxed(val, base + vctl_offset);
|
||||
mb();
|
||||
udelay(timeout);
|
||||
|
||||
/* Enable the CPU supply regulator*/
|
||||
val = 0x30080;
|
||||
writel_relaxed(val, base + vctl_offset);
|
||||
mb();
|
||||
udelay(timeout);
|
||||
|
||||
iounmap(base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_turn_on_cpu_rail);
|
||||
|
||||
void msm_spm_reinit(void)
|
||||
{
|
||||
unsigned int cpu;
|
||||
for_each_possible_cpu(cpu)
|
||||
msm_spm_drv_reinit(
|
||||
&per_cpu(msm_cpu_spm_device.reg_data, cpu), true);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_reinit);
|
||||
|
||||
/*
|
||||
* msm_spm_is_mode_avail() - Specifies if a mode is available for the cpu
|
||||
* It should only be used to decide a mode before lpm driver is probed.
|
||||
* @mode: SPM LPM mode to be selected
|
||||
*/
|
||||
bool msm_spm_is_mode_avail(unsigned int mode)
|
||||
{
|
||||
struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dev->num_modes; i++) {
|
||||
if (dev->modes[i].mode == mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* msm_spm_is_avs_enabled() - Functions returns 1 if AVS is enabled and
|
||||
* 0 if it is not.
|
||||
* @cpu: specifies cpu's avs should be read
|
||||
*
|
||||
* Returns errno in case of failure or AVS enable state otherwise
|
||||
*/
|
||||
int msm_spm_is_avs_enabled(unsigned int cpu)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_get_avs_enable(&dev->reg_data);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_is_avs_enabled);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_enable() - Enables AVS on the SAW that controls this cpu's
|
||||
* voltage.
|
||||
* @cpu: specifies which cpu's avs should be enabled
|
||||
*
|
||||
* Returns errno in case of failure or 0 if successful
|
||||
*/
|
||||
int msm_spm_avs_enable(unsigned int cpu)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_avs_enable(&dev->reg_data, true);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_enable);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_disable() - Disables AVS on the SAW that controls this cpu's
|
||||
* voltage.
|
||||
* @cpu: specifies which cpu's avs should be enabled
|
||||
*
|
||||
* Returns errno in case of failure or 0 if successful
|
||||
*/
|
||||
int msm_spm_avs_disable(unsigned int cpu)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_avs_enable(&dev->reg_data, false);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_disable);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_set_limit() - Set maximum and minimum AVS limits on the
|
||||
* SAW that controls this cpu's voltage.
|
||||
* @cpu: specify which cpu's avs should be configured
|
||||
* @min_lvl: specifies the minimum PMIC output voltage control register
|
||||
* value that may be sent to the PMIC
|
||||
* @max_lvl: specifies the maximum PMIC output voltage control register
|
||||
* value that may be sent to the PMIC
|
||||
* Returns errno in case of failure or 0 if successful
|
||||
*/
|
||||
int msm_spm_avs_set_limit(unsigned int cpu,
|
||||
uint32_t min_lvl, uint32_t max_lvl)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_avs_limit(&dev->reg_data, min_lvl, max_lvl);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_set_limit);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_enable_irq() - Enable an AVS interrupt
|
||||
* @cpu: specifies which CPU's AVS should be configured
|
||||
* @irq: specifies which interrupt to enable
|
||||
*
|
||||
* Returns errno in case of failure or 0 if successful.
|
||||
*/
|
||||
int msm_spm_avs_enable_irq(unsigned int cpu, enum msm_spm_avs_irq irq)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, true);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_enable_irq);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_disable_irq() - Disable an AVS interrupt
|
||||
* @cpu: specifies which CPU's AVS should be configured
|
||||
* @irq: specifies which interrupt to disable
|
||||
*
|
||||
* Returns errno in case of failure or 0 if successful.
|
||||
*/
|
||||
int msm_spm_avs_disable_irq(unsigned int cpu, enum msm_spm_avs_irq irq)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_avs_irq_enable(&dev->reg_data, irq, false);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_disable_irq);
|
||||
|
||||
/**
|
||||
* msm_spm_avs_clear_irq() - Clear a latched AVS interrupt
|
||||
* @cpu: specifies which CPU's AVS should be configured
|
||||
* @irq: specifies which interrupt to clear
|
||||
*
|
||||
* Returns errno in case of failure or 0 if successful.
|
||||
*/
|
||||
int msm_spm_avs_clear_irq(unsigned int cpu, enum msm_spm_avs_irq irq)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_avs_clear_irq(&dev->reg_data, irq);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_avs_clear_irq);
|
||||
|
||||
/**
|
||||
* msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
|
||||
* @mode: SPM LPM mode to enter
|
||||
* @notify_rpm: Notify RPM in this mode
|
||||
*/
|
||||
int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm)
|
||||
{
|
||||
struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
|
||||
return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_set_low_power_mode);
|
||||
|
||||
/**
|
||||
* msm_spm_init(): Board initalization function
|
||||
* @data: platform specific SPM register configuration data
|
||||
* @nr_devs: Number of SPM devices being initialized
|
||||
*/
|
||||
int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs)
|
||||
{
|
||||
unsigned int cpu;
|
||||
int ret = 0;
|
||||
|
||||
BUG_ON((nr_devs < num_possible_cpus()) || !data);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu);
|
||||
ret = msm_spm_dev_init(dev, &data[cpu]);
|
||||
if (ret < 0) {
|
||||
pr_warn("%s():failed CPU:%u ret:%d\n", __func__,
|
||||
cpu, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct msm_spm_device *msm_spm_get_device_by_name(const char *name)
|
||||
{
|
||||
struct list_head *list;
|
||||
|
||||
list_for_each(list, &spm_list) {
|
||||
struct msm_spm_device *dev
|
||||
= list_entry(list, typeof(*dev), list);
|
||||
if (dev->name && !strcmp(dev->name, name))
|
||||
return dev;
|
||||
}
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
int msm_spm_config_low_power_mode(struct msm_spm_device *dev,
|
||||
unsigned int mode, bool notify_rpm)
|
||||
{
|
||||
return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm);
|
||||
}
|
||||
#ifdef CONFIG_MSM_L2_SPM
|
||||
|
||||
/**
|
||||
* msm_spm_apcs_set_phase(): Set number of SMPS phases.
|
||||
* @cpu: cpu which is requesting the change in number of phases.
|
||||
* @phase_cnt: Number of phases to be set active
|
||||
*/
|
||||
int msm_spm_apcs_set_phase(int cpu, unsigned int phase_cnt)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_pmic_data(&dev->reg_data,
|
||||
MSM_SPM_PMIC_PHASE_PORT, phase_cnt);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_apcs_set_phase);
|
||||
|
||||
/** msm_spm_enable_fts_lpm() : Enable FTS to switch to low power
|
||||
* when the cores are in low power modes
|
||||
* @cpu: cpu that is entering low power mode.
|
||||
* @mode: The mode configuration for FTS
|
||||
*/
|
||||
int msm_spm_enable_fts_lpm(int cpu, uint32_t mode)
|
||||
{
|
||||
struct msm_spm_device *dev = per_cpu(cpu_vctl_device, cpu);
|
||||
|
||||
if (!dev)
|
||||
return -ENXIO;
|
||||
|
||||
return msm_spm_drv_set_pmic_data(&dev->reg_data,
|
||||
MSM_SPM_PMIC_PFM_PORT, mode);
|
||||
}
|
||||
EXPORT_SYMBOL(msm_spm_enable_fts_lpm);
|
||||
|
||||
#endif
|
||||
|
||||
static int get_cpu_id(struct device_node *node)
|
||||
{
|
||||
struct device_node *cpu_node;
|
||||
u32 cpu;
|
||||
char *key = "qcom,cpu";
|
||||
|
||||
cpu_node = of_parse_phandle(node, key, 0);
|
||||
if (cpu_node) {
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (of_get_cpu_node(cpu, NULL) == cpu_node)
|
||||
return cpu;
|
||||
}
|
||||
} else
|
||||
return num_possible_cpus();
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_spm_device *dev = NULL;
|
||||
const char *val = NULL;
|
||||
char *key = "qcom,name";
|
||||
int cpu = get_cpu_id(pdev->dev.of_node);
|
||||
|
||||
if ((cpu >= 0) && cpu < num_possible_cpus())
|
||||
dev = &per_cpu(msm_cpu_spm_device, cpu);
|
||||
else if (cpu == num_possible_cpus())
|
||||
dev = devm_kzalloc(&pdev->dev, sizeof(struct msm_spm_device),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!dev)
|
||||
return NULL;
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, key, &val)) {
|
||||
pr_err("%s(): Cannot find a required node key:%s\n",
|
||||
__func__, key);
|
||||
return NULL;
|
||||
}
|
||||
dev->name = val;
|
||||
list_add(&dev->list, &spm_list);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void get_cpumask(struct device_node *node, struct cpumask *mask)
|
||||
{
|
||||
unsigned c;
|
||||
int idx = 0;
|
||||
struct device_node *cpu_node;
|
||||
char *key = "qcom,cpu-vctl-list";
|
||||
|
||||
cpu_node = of_parse_phandle(node, key, idx++);
|
||||
while (cpu_node) {
|
||||
for_each_possible_cpu(c) {
|
||||
if (of_get_cpu_node(c, NULL) == cpu_node)
|
||||
cpumask_set_cpu(c, mask);
|
||||
}
|
||||
cpu_node = of_parse_phandle(node, key, idx++);
|
||||
};
|
||||
}
|
||||
|
||||
static int msm_spm_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
int cpu = 0;
|
||||
int i = 0;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *n = NULL;
|
||||
struct msm_spm_platform_data spm_data;
|
||||
char *key = NULL;
|
||||
uint32_t val = 0;
|
||||
struct msm_spm_seq_entry modes[MSM_SPM_MODE_NR];
|
||||
int len = 0;
|
||||
struct msm_spm_device *dev = NULL;
|
||||
struct resource *res = NULL;
|
||||
uint32_t mode_count = 0;
|
||||
|
||||
struct spm_of {
|
||||
char *key;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
struct spm_of spm_of_data[] = {
|
||||
{"qcom,saw2-cfg", MSM_SPM_REG_SAW_CFG},
|
||||
{"qcom,saw2-avs-ctl", MSM_SPM_REG_SAW_AVS_CTL},
|
||||
{"qcom,saw2-avs-hysteresis", MSM_SPM_REG_SAW_AVS_HYSTERESIS},
|
||||
{"qcom,saw2-avs-limit", MSM_SPM_REG_SAW_AVS_LIMIT},
|
||||
{"qcom,saw2-avs-dly", MSM_SPM_REG_SAW_AVS_DLY},
|
||||
{"qcom,saw2-spm-dly", MSM_SPM_REG_SAW_SPM_DLY},
|
||||
{"qcom,saw2-spm-ctl", MSM_SPM_REG_SAW_SPM_CTL},
|
||||
{"qcom,saw2-pmic-data0", MSM_SPM_REG_SAW_PMIC_DATA_0},
|
||||
{"qcom,saw2-pmic-data1", MSM_SPM_REG_SAW_PMIC_DATA_1},
|
||||
{"qcom,saw2-pmic-data2", MSM_SPM_REG_SAW_PMIC_DATA_2},
|
||||
{"qcom,saw2-pmic-data3", MSM_SPM_REG_SAW_PMIC_DATA_3},
|
||||
{"qcom,saw2-pmic-data4", MSM_SPM_REG_SAW_PMIC_DATA_4},
|
||||
{"qcom,saw2-pmic-data5", MSM_SPM_REG_SAW_PMIC_DATA_5},
|
||||
{"qcom,saw2-pmic-data6", MSM_SPM_REG_SAW_PMIC_DATA_6},
|
||||
{"qcom,saw2-pmic-data7", MSM_SPM_REG_SAW_PMIC_DATA_7},
|
||||
};
|
||||
|
||||
struct mode_of {
|
||||
char *key;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
struct mode_of mode_of_data[] = {
|
||||
{"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING},
|
||||
{"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION},
|
||||
{"qcom,saw2-spm-cmd-gdhs", MSM_SPM_MODE_GDHS},
|
||||
{"qcom,saw2-spm-cmd-spc",
|
||||
MSM_SPM_MODE_STANDALONE_POWER_COLLAPSE},
|
||||
{"qcom,saw2-spm-cmd-pc", MSM_SPM_MODE_POWER_COLLAPSE},
|
||||
{"qcom,saw2-spm-cmd-fpc", MSM_SPM_MODE_FASTPC},
|
||||
};
|
||||
|
||||
dev = msm_spm_get_device(pdev);
|
||||
if (!dev) {
|
||||
/*
|
||||
* For partial goods support some CPUs might not be available
|
||||
* in which case, shouldn't throw an error
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
get_cpumask(node, &dev->mask);
|
||||
|
||||
memset(&spm_data, 0, sizeof(struct msm_spm_platform_data));
|
||||
memset(&modes, 0,
|
||||
(MSM_SPM_MODE_NR - 2) * sizeof(struct msm_spm_seq_entry));
|
||||
|
||||
key = "qcom,saw2-ver-reg";
|
||||
ret = of_property_read_u32(node, key, &val);
|
||||
if (ret)
|
||||
goto fail;
|
||||
spm_data.ver_reg = val;
|
||||
|
||||
key = "qcom,vctl-timeout-us";
|
||||
ret = of_property_read_u32(node, key, &val);
|
||||
if (!ret)
|
||||
spm_data.vctl_timeout_us = val;
|
||||
|
||||
/* SAW start address */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
ret = -EFAULT;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
spm_data.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!spm_data.reg_base_addr) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
spm_data.vctl_port = -1;
|
||||
spm_data.phase_port = -1;
|
||||
spm_data.pfm_port = -1;
|
||||
|
||||
key = "qcom,vctl-port";
|
||||
of_property_read_u32(node, key, &spm_data.vctl_port);
|
||||
|
||||
key = "qcom,phase-port";
|
||||
of_property_read_u32(node, key, &spm_data.phase_port);
|
||||
|
||||
key = "qcom,pfm-port";
|
||||
of_property_read_u32(node, key, &spm_data.pfm_port);
|
||||
|
||||
/* Q2S (QChannel-2-SPM) register */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "q2s");
|
||||
if (res) {
|
||||
dev->q2s_reg = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!dev->q2s_reg) {
|
||||
pr_err("%s(): Unable to iomap Q2S register\n",
|
||||
__func__);
|
||||
ret = -EADDRNOTAVAIL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
key = "qcom,use-qchannel-for-pc";
|
||||
dev->qchannel_ignore = !of_property_read_bool(node, key);
|
||||
|
||||
key = "qcom,use-spm-clock-gating";
|
||||
dev->use_spm_clk_gating = of_property_read_bool(node, key);
|
||||
|
||||
key = "qcom,use-qchannel-for-wfi";
|
||||
dev->use_qchannel_for_wfi = of_property_read_bool(node, key);
|
||||
|
||||
/* HW flush address */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hw-flush");
|
||||
if (res) {
|
||||
dev->flush_base_addr = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(dev->flush_base_addr)) {
|
||||
ret = PTR_ERR(dev->flush_base_addr);
|
||||
pr_err("%s(): Unable to iomap hw flush register %d\n",
|
||||
__func__, ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sleep req address */
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slpreq");
|
||||
if (res) {
|
||||
dev->slpreq_base_addr = devm_ioremap(&pdev->dev, res->start,
|
||||
resource_size(res));
|
||||
if (!dev->slpreq_base_addr) {
|
||||
ret = -ENOMEM;
|
||||
pr_err("%s(): Unable to iomap slpreq register\n",
|
||||
__func__);
|
||||
ret = -EADDRNOTAVAIL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* At system boot, cpus and or clusters can remain in reset. CCI SPM
|
||||
* will not be triggered unless SPM_LEGACY_MODE bit is set for the
|
||||
* cluster in reset. Initialize q2s registers and set the
|
||||
* SPM_LEGACY_MODE bit.
|
||||
*/
|
||||
msm_spm_config_q2s(dev, MSM_SPM_MODE_POWER_COLLAPSE);
|
||||
msm_spm_drv_reg_init(&dev->reg_data, &spm_data);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
|
||||
ret = of_property_read_u32(node, spm_of_data[i].key, &val);
|
||||
if (ret)
|
||||
continue;
|
||||
msm_spm_drv_upd_reg_shadow(&dev->reg_data, spm_of_data[i].id,
|
||||
val);
|
||||
}
|
||||
|
||||
for_each_child_of_node(node, n) {
|
||||
const char *name;
|
||||
bool bit_set;
|
||||
int sync;
|
||||
|
||||
if (!n->name)
|
||||
continue;
|
||||
|
||||
ret = of_property_read_string(n, "qcom,label", &name);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mode_of_data); i++)
|
||||
if (!strcmp(name, mode_of_data[i].key))
|
||||
break;
|
||||
|
||||
if (i == ARRAY_SIZE(mode_of_data)) {
|
||||
pr_err("Mode name invalid %s\n", name);
|
||||
break;
|
||||
}
|
||||
|
||||
modes[mode_count].mode = mode_of_data[i].id;
|
||||
modes[mode_count].cmd =
|
||||
(uint8_t *)of_get_property(n, "qcom,sequence", &len);
|
||||
if (!modes[mode_count].cmd) {
|
||||
pr_err("cmd is empty\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
bit_set = of_property_read_bool(n, "qcom,pc_mode");
|
||||
modes[mode_count].ctl |= bit_set ? BIT(PC_MODE_BIT) : 0;
|
||||
|
||||
bit_set = of_property_read_bool(n, "qcom,ret_mode");
|
||||
modes[mode_count].ctl |= bit_set ? BIT(RET_MODE_BIT) : 0;
|
||||
|
||||
bit_set = of_property_read_bool(n, "qcom,slp_cmd_mode");
|
||||
modes[mode_count].ctl |= bit_set ? BIT(SLP_CMD_BIT) : 0;
|
||||
|
||||
bit_set = of_property_read_bool(n, "qcom,isar");
|
||||
modes[mode_count].ctl |= bit_set ? BIT(ISAR_BIT) : 0;
|
||||
|
||||
bit_set = of_property_read_bool(n, "qcom,spm_en");
|
||||
modes[mode_count].ctl |= bit_set ? BIT(SPM_EN_BIT) : 0;
|
||||
|
||||
ret = of_property_read_u32(n, "qcom,event_sync", &sync);
|
||||
if (!ret)
|
||||
modes[mode_count].ctl |= sync << EVENT_SYNC_BIT;
|
||||
|
||||
mode_count++;
|
||||
}
|
||||
|
||||
spm_data.modes = modes;
|
||||
spm_data.num_modes = mode_count;
|
||||
|
||||
key = "qcom,supports-rpm-hs";
|
||||
dev->allow_rpm_hs = of_property_read_bool(pdev->dev.of_node, key);
|
||||
|
||||
ret = msm_spm_dev_init(dev, &spm_data);
|
||||
if (ret)
|
||||
pr_err("SPM modes programming is not available from HLOS\n");
|
||||
|
||||
platform_set_drvdata(pdev, dev);
|
||||
|
||||
for_each_cpu(cpu, &dev->mask)
|
||||
per_cpu(cpu_vctl_device, cpu) = dev;
|
||||
|
||||
if (!spm_data.num_modes)
|
||||
return 0;
|
||||
|
||||
cpu = get_cpu_id(pdev->dev.of_node);
|
||||
|
||||
/* For CPUs that are online, the SPM has to be programmed for
|
||||
* clockgating mode to ensure that it can use SPM for entering these
|
||||
* low power modes.
|
||||
*/
|
||||
get_online_cpus();
|
||||
if ((cpu >= 0) && (cpu < num_possible_cpus()) && (cpu_online(cpu)))
|
||||
msm_spm_config_low_power_mode(dev, MSM_SPM_MODE_CLOCK_GATING,
|
||||
false);
|
||||
put_online_cpus();
|
||||
return ret;
|
||||
|
||||
fail:
|
||||
cpu = get_cpu_id(pdev->dev.of_node);
|
||||
if (dev && (cpu >= num_possible_cpus() || (cpu < 0))) {
|
||||
for_each_cpu(cpu, &dev->mask)
|
||||
per_cpu(cpu_vctl_device, cpu) = ERR_PTR(ret);
|
||||
}
|
||||
|
||||
pr_err("%s: CPU%d SPM device probe failed: %d\n", __func__, cpu, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_spm_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_spm_device *dev = platform_get_drvdata(pdev);
|
||||
list_del(&dev->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id msm_spm_match_table[] = {
|
||||
{.compatible = "qcom,spm-v2"},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver msm_spm_device_driver = {
|
||||
.probe = msm_spm_dev_probe,
|
||||
.remove = msm_spm_dev_remove,
|
||||
.driver = {
|
||||
.name = "spm-v2",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = msm_spm_match_table,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* msm_spm_device_init(): Device tree initialization function
|
||||
*/
|
||||
int __init msm_spm_device_init(void)
|
||||
{
|
||||
static bool registered;
|
||||
if (registered)
|
||||
return 0;
|
||||
registered = true;
|
||||
return platform_driver_register(&msm_spm_device_driver);
|
||||
}
|
||||
arch_initcall(msm_spm_device_init);
|
134
drivers/soc/qcom/spm_driver.h
Normal file
134
drivers/soc/qcom/spm_driver.h
Normal file
|
@ -0,0 +1,134 @@
|
|||
/* Copyright (c) 2011-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.
|
||||
*/
|
||||
#ifndef __ARCH_ARM_MACH_MSM_SPM_DEVICES_H
|
||||
#define __ARCH_ARM_MACH_MSM_SPM_DEVICES_H
|
||||
|
||||
#include <soc/qcom/spm.h>
|
||||
|
||||
enum {
|
||||
MSM_SPM_REG_SAW_CFG,
|
||||
MSM_SPM_REG_SAW_AVS_CTL,
|
||||
MSM_SPM_REG_SAW_AVS_HYSTERESIS,
|
||||
MSM_SPM_REG_SAW_SPM_CTL,
|
||||
MSM_SPM_REG_SAW_PMIC_DLY,
|
||||
MSM_SPM_REG_SAW_AVS_LIMIT,
|
||||
MSM_SPM_REG_SAW_AVS_DLY,
|
||||
MSM_SPM_REG_SAW_SPM_DLY,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_0,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_1,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_2,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_3,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_4,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_5,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_6,
|
||||
MSM_SPM_REG_SAW_PMIC_DATA_7,
|
||||
MSM_SPM_REG_SAW_RST,
|
||||
|
||||
MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW_RST,
|
||||
|
||||
MSM_SPM_REG_SAW_ID,
|
||||
MSM_SPM_REG_SAW_SECURE,
|
||||
MSM_SPM_REG_SAW_STS0,
|
||||
MSM_SPM_REG_SAW_STS1,
|
||||
MSM_SPM_REG_SAW_STS2,
|
||||
MSM_SPM_REG_SAW_VCTL,
|
||||
MSM_SPM_REG_SAW_SEQ_ENTRY,
|
||||
MSM_SPM_REG_SAW_SPM_STS,
|
||||
MSM_SPM_REG_SAW_AVS_STS,
|
||||
MSM_SPM_REG_SAW_PMIC_STS,
|
||||
MSM_SPM_REG_SAW_VERSION,
|
||||
|
||||
MSM_SPM_REG_NR,
|
||||
};
|
||||
|
||||
struct msm_spm_seq_entry {
|
||||
uint32_t mode;
|
||||
uint8_t *cmd;
|
||||
uint32_t ctl;
|
||||
};
|
||||
|
||||
struct msm_spm_platform_data {
|
||||
void __iomem *reg_base_addr;
|
||||
uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE];
|
||||
|
||||
uint32_t ver_reg;
|
||||
uint32_t vctl_port;
|
||||
uint32_t phase_port;
|
||||
uint32_t pfm_port;
|
||||
|
||||
uint8_t awake_vlevel;
|
||||
uint32_t vctl_timeout_us;
|
||||
uint32_t avs_timeout_us;
|
||||
|
||||
uint32_t num_modes;
|
||||
struct msm_spm_seq_entry *modes;
|
||||
};
|
||||
|
||||
enum msm_spm_pmic_port {
|
||||
MSM_SPM_PMIC_VCTL_PORT,
|
||||
MSM_SPM_PMIC_PHASE_PORT,
|
||||
MSM_SPM_PMIC_PFM_PORT,
|
||||
};
|
||||
|
||||
struct msm_spm_driver_data {
|
||||
uint32_t major;
|
||||
uint32_t minor;
|
||||
uint32_t ver_reg;
|
||||
uint32_t vctl_port;
|
||||
uint32_t phase_port;
|
||||
uint32_t pfm_port;
|
||||
void __iomem *reg_base_addr;
|
||||
uint32_t vctl_timeout_us;
|
||||
uint32_t avs_timeout_us;
|
||||
uint32_t reg_shadow[MSM_SPM_REG_NR];
|
||||
uint32_t *reg_seq_entry_shadow;
|
||||
uint32_t *reg_offsets;
|
||||
};
|
||||
|
||||
int msm_spm_drv_init(struct msm_spm_driver_data *dev,
|
||||
struct msm_spm_platform_data *data);
|
||||
int msm_spm_drv_reg_init(struct msm_spm_driver_data *dev,
|
||||
struct msm_spm_platform_data *data);
|
||||
void msm_spm_drv_reinit(struct msm_spm_driver_data *dev, bool seq);
|
||||
int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev,
|
||||
uint32_t ctl);
|
||||
int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev,
|
||||
unsigned int vlevel);
|
||||
void dump_regs(struct msm_spm_driver_data *dev, int cpu);
|
||||
uint32_t msm_spm_drv_get_sts_curr_pmic_data(
|
||||
struct msm_spm_driver_data *dev);
|
||||
int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev,
|
||||
uint8_t *cmd, uint32_t *offset);
|
||||
void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev);
|
||||
int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev,
|
||||
bool enable);
|
||||
int msm_spm_drv_set_pmic_data(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_pmic_port port, unsigned int data);
|
||||
|
||||
int msm_spm_drv_set_avs_limit(struct msm_spm_driver_data *dev,
|
||||
uint32_t min_lvl, uint32_t max_lvl);
|
||||
|
||||
int msm_spm_drv_set_avs_enable(struct msm_spm_driver_data *dev,
|
||||
bool enable);
|
||||
int msm_spm_drv_get_avs_enable(struct msm_spm_driver_data *dev);
|
||||
|
||||
int msm_spm_drv_set_avs_irq_enable(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_avs_irq irq, bool enable);
|
||||
int msm_spm_drv_avs_clear_irq(struct msm_spm_driver_data *dev,
|
||||
enum msm_spm_avs_irq irq);
|
||||
|
||||
void msm_spm_reinit(void);
|
||||
int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs);
|
||||
void msm_spm_drv_upd_reg_shadow(struct msm_spm_driver_data *dev, int id,
|
||||
int val);
|
||||
uint32_t msm_spm_drv_get_vdd(struct msm_spm_driver_data *dev);
|
||||
#endif
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
|
||||
/* Copyright (c) 2010-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
|
||||
|
|
Loading…
Add table
Reference in a new issue