Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit de64ab2e authored by Abhijeet Dharmapurikar's avatar Abhijeet Dharmapurikar Committed by Rohit Vaswani
Browse files

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: default avatarMahesh Sivasubramanian <msivasub@codeaurora.org>
parent f9157b4e
Loading
Loading
Loading
Loading
+169 −0
Original line number Diff line number Diff line
* 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>;
};
+1 −0
Original line number Diff line number Diff line
@@ -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
+8 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/* 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;
}
Loading