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:
Abhijeet Dharmapurikar 2016-01-19 10:43:39 -08:00 committed by Rohit Vaswani
parent f9157b4ed2
commit de64ab2e86
8 changed files with 1981 additions and 1 deletions

View 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>;
};

View file

@ -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

View file

@ -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

View file

@ -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
View 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;
}

View 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);

View 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

View file

@ -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