blob: 95100168f391022078b9fee332a73eea1636ad80 [file] [log] [blame]
/*
* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/msm-ldo-regulator.h>
#include <soc/qcom/spm.h>
#include "cpr3-regulator.h"
#define CPR3_REGULATOR_CORNER_INVALID (-1)
#define CPR3_RO_MASK GENMASK(CPR3_RO_COUNT - 1, 0)
/* CPR3 registers */
#define CPR3_REG_CPR_VERSION 0x0
#define CPRH_CPR_VERSION_4P5 0x40050000
#define CPR3_REG_CPR_CTL 0x4
#define CPR3_CPR_CTL_LOOP_EN_MASK BIT(0)
#define CPR3_CPR_CTL_LOOP_ENABLE BIT(0)
#define CPR3_CPR_CTL_LOOP_DISABLE 0
#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK GENMASK(5, 1)
#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT 1
#define CPR3_CPR_CTL_COUNT_MODE_MASK GENMASK(7, 6)
#define CPR3_CPR_CTL_COUNT_MODE_SHIFT 6
#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN 0
#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX 1
#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED 2
#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE 3
#define CPR3_CPR_CTL_COUNT_REPEAT_MASK GENMASK(31, 9)
#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT 9
#define CPR3_REG_CPR_STATUS 0x8
#define CPR3_CPR_STATUS_BUSY_MASK BIT(0)
#define CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK BIT(1)
/*
* This register is not present on controllers that support HW closed-loop
* except CPR4 APSS controller.
*/
#define CPR3_REG_CPR_TIMER_AUTO_CONT 0xC
#define CPR3_REG_CPR_STEP_QUOT 0x14
#define CPR3_CPR_STEP_QUOT_MIN_MASK GENMASK(5, 0)
#define CPR3_CPR_STEP_QUOT_MIN_SHIFT 0
#define CPR3_CPR_STEP_QUOT_MAX_MASK GENMASK(11, 6)
#define CPR3_CPR_STEP_QUOT_MAX_SHIFT 6
#define CPR3_REG_GCNT(ro) (0xA0 + 0x4 * (ro))
#define CPR3_REG_SENSOR_BYPASS_WRITE(sensor) (0xE0 + 0x4 * ((sensor) / 32))
#define CPR3_REG_SENSOR_BYPASS_WRITE_BANK(bank) (0xE0 + 0x4 * (bank))
#define CPR3_REG_SENSOR_MASK_WRITE(sensor) (0x120 + 0x4 * ((sensor) / 32))
#define CPR3_REG_SENSOR_MASK_WRITE_BANK(bank) (0x120 + 0x4 * (bank))
#define CPR3_REG_SENSOR_MASK_READ(sensor) (0x140 + 0x4 * ((sensor) / 32))
#define CPR3_REG_SENSOR_OWNER(sensor) (0x200 + 0x4 * (sensor))
#define CPR3_REG_CONT_CMD 0x800
#define CPR3_CONT_CMD_ACK 0x1
#define CPR3_CONT_CMD_NACK 0x0
#define CPR3_REG_THRESH(thread) (0x808 + 0x440 * (thread))
#define CPR3_THRESH_CONS_DOWN_MASK GENMASK(3, 0)
#define CPR3_THRESH_CONS_DOWN_SHIFT 0
#define CPR3_THRESH_CONS_UP_MASK GENMASK(7, 4)
#define CPR3_THRESH_CONS_UP_SHIFT 4
#define CPR3_THRESH_DOWN_THRESH_MASK GENMASK(12, 8)
#define CPR3_THRESH_DOWN_THRESH_SHIFT 8
#define CPR3_THRESH_UP_THRESH_MASK GENMASK(17, 13)
#define CPR3_THRESH_UP_THRESH_SHIFT 13
#define CPR3_REG_RO_MASK(thread) (0x80C + 0x440 * (thread))
#define CPR3_REG_RESULT0(thread) (0x810 + 0x440 * (thread))
#define CPR3_RESULT0_BUSY_MASK BIT(0)
#define CPR3_RESULT0_STEP_DN_MASK BIT(1)
#define CPR3_RESULT0_STEP_UP_MASK BIT(2)
#define CPR3_RESULT0_ERROR_STEPS_MASK GENMASK(7, 3)
#define CPR3_RESULT0_ERROR_STEPS_SHIFT 3
#define CPR3_RESULT0_ERROR_MASK GENMASK(19, 8)
#define CPR3_RESULT0_ERROR_SHIFT 8
#define CPR3_RESULT0_NEGATIVE_MASK BIT(20)
#define CPR3_REG_RESULT1(thread) (0x814 + 0x440 * (thread))
#define CPR3_RESULT1_QUOT_MIN_MASK GENMASK(11, 0)
#define CPR3_RESULT1_QUOT_MIN_SHIFT 0
#define CPR3_RESULT1_QUOT_MAX_MASK GENMASK(23, 12)
#define CPR3_RESULT1_QUOT_MAX_SHIFT 12
#define CPR3_RESULT1_RO_MIN_MASK GENMASK(27, 24)
#define CPR3_RESULT1_RO_MIN_SHIFT 24
#define CPR3_RESULT1_RO_MAX_MASK GENMASK(31, 28)
#define CPR3_RESULT1_RO_MAX_SHIFT 28
#define CPR3_REG_RESULT2(thread) (0x818 + 0x440 * (thread))
#define CPR3_RESULT2_STEP_QUOT_MIN_MASK GENMASK(5, 0)
#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT 0
#define CPR3_RESULT2_STEP_QUOT_MAX_MASK GENMASK(11, 6)
#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT 6
#define CPR3_RESULT2_SENSOR_MIN_MASK GENMASK(23, 16)
#define CPR3_RESULT2_SENSOR_MIN_SHIFT 16
#define CPR3_RESULT2_SENSOR_MAX_MASK GENMASK(31, 24)
#define CPR3_RESULT2_SENSOR_MAX_SHIFT 24
#define CPR3_REG_IRQ_EN 0x81C
#define CPR3_REG_IRQ_CLEAR 0x820
#define CPR3_REG_IRQ_STATUS 0x824
#define CPR3_IRQ_UP BIT(3)
#define CPR3_IRQ_MID BIT(2)
#define CPR3_IRQ_DOWN BIT(1)
#define CPR3_REG_TARGET_QUOT(thread, ro) \
(0x840 + 0x440 * (thread) + 0x4 * (ro))
/* Registers found only on controllers that support HW closed-loop. */
#define CPR3_REG_PD_THROTTLE 0xE8
#define CPR3_PD_THROTTLE_DISABLE 0x0
#define CPR3_REG_HW_CLOSED_LOOP 0x3000
#define CPR3_HW_CLOSED_LOOP_ENABLE 0x0
#define CPR3_HW_CLOSED_LOOP_DISABLE 0x1
#define CPR3_REG_CPR_TIMER_MID_CONT 0x3004
#define CPR3_REG_CPR_TIMER_UP_DN_CONT 0x3008
#define CPR3_REG_LAST_MEASUREMENT 0x7F8
#define CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT 0
#define CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT 4
#define CPR3_LAST_MEASUREMENT_THREAD_DN(thread) \
(BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT)
#define CPR3_LAST_MEASUREMENT_THREAD_UP(thread) \
(BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT)
#define CPR3_LAST_MEASUREMENT_AGGR_DN BIT(8)
#define CPR3_LAST_MEASUREMENT_AGGR_MID BIT(9)
#define CPR3_LAST_MEASUREMENT_AGGR_UP BIT(10)
#define CPR3_LAST_MEASUREMENT_VALID BIT(11)
#define CPR3_LAST_MEASUREMENT_SAW_ERROR BIT(12)
#define CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK GENMASK(23, 16)
#define CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT 16
/* CPR4 controller specific registers and bit definitions */
#define CPR4_REG_CPR_TIMER_CLAMP 0x10
#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN BIT(27)
#define CPR4_REG_MISC 0x700
#define CPR4_MISC_RESET_STEP_QUOT_LOOP_EN BIT(2)
#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK GENMASK(23, 20)
#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT 20
#define CPR4_MISC_TEMP_SENSOR_ID_START_MASK GENMASK(27, 24)
#define CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT 24
#define CPR4_MISC_TEMP_SENSOR_ID_END_MASK GENMASK(31, 28)
#define CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT 28
#define CPR4_REG_SAW_ERROR_STEP_LIMIT 0x7A4
#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK GENMASK(4, 0)
#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT 0
#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK GENMASK(9, 5)
#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT 5
#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS 0x7A8
#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK GENMASK(28, 18)
#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT 18
#define CPR4_REG_MARGIN_TEMP_CORE(core) (0x7AC + 0x4 * (core))
#define CPR4_MARGIN_TEMP_CORE_ADJ_MASK GENMASK(7, 0)
#define CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT 8
#define CPR4_REG_MARGIN_TEMP_POINT0N1 0x7F0
#define CPR4_MARGIN_TEMP_POINT0_MASK GENMASK(11, 0)
#define CPR4_MARGIN_TEMP_POINT0_SHIFT 0
#define CPR4_MARGIN_TEMP_POINT1_MASK GENMASK(23, 12)
#define CPR4_MARGIN_TEMP_POINT1_SHIFT 12
#define CPR4_REG_MARGIN_TEMP_POINT2 0x7F4
#define CPR4_MARGIN_TEMP_POINT2_MASK GENMASK(11, 0)
#define CPR4_MARGIN_TEMP_POINT2_SHIFT 0
#define CPR4_REG_MARGIN_ADJ_CTL 0x7F8
#define CPR4_MARGIN_ADJ_CTL_BOOST_EN BIT(0)
#define CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN BIT(1)
#define CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN BIT(2)
#define CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN BIT(3)
#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK BIT(4)
#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE BIT(4)
#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE 0
#define CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN BIT(7)
#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN BIT(8)
#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK GENMASK(16, 12)
#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT 12
#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK GENMASK(21, 19)
#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT 19
#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK GENMASK(25, 22)
#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT 22
#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK GENMASK(31, 26)
#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT 26
#define CPR4_REG_CPR_MASK_THREAD(thread) (0x80C + 0x440 * (thread))
#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31)
#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0)
/* CPRh controller specific registers and bit definitions */
#define CPRH_REG_CORNER(thread, corner) \
((thread)->ctrl->cpr_hw_version >= CPRH_CPR_VERSION_4P5 \
? 0x3500 + 0xA0 * (thread)->thread_id + 0x4 * (corner) \
: 0x3A00 + 0x4 * (corner))
#define CPRH_CORNER_INIT_VOLTAGE_MASK GENMASK(7, 0)
#define CPRH_CORNER_INIT_VOLTAGE_SHIFT 0
#define CPRH_CORNER_FLOOR_VOLTAGE_MASK GENMASK(15, 8)
#define CPRH_CORNER_FLOOR_VOLTAGE_SHIFT 8
#define CPRH_CORNER_QUOT_DELTA_MASK GENMASK(24, 16)
#define CPRH_CORNER_QUOT_DELTA_SHIFT 16
#define CPRH_CORNER_RO_SEL_MASK GENMASK(28, 25)
#define CPRH_CORNER_RO_SEL_SHIFT 25
#define CPRH_CORNER_CPR_CL_DISABLE BIT(29)
#define CPRH_CORNER_CORE_TEMP_MARGIN_DISABLE BIT(30)
#define CPRH_CORNER_LAST_KNOWN_VOLTAGE_ENABLE BIT(31)
#define CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE 255
#define CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE 255
#define CPRH_CORNER_QUOT_DELTA_MAX_VALUE 511
#define CPRH_REG_CTL(ctrl) \
((ctrl)->cpr_hw_version >= CPRH_CPR_VERSION_4P5 ? 0x3A80 : 0x3AA0)
#define CPRH_CTL_OSM_ENABLED BIT(0)
#define CPRH_CTL_BASE_VOLTAGE_MASK GENMASK(10, 1)
#define CPRH_CTL_BASE_VOLTAGE_SHIFT 1
#define CPRH_CTL_INIT_MODE_MASK GENMASK(16, 11)
#define CPRH_CTL_INIT_MODE_SHIFT 11
#define CPRH_CTL_MODE_SWITCH_DELAY_MASK GENMASK(24, 17)
#define CPRH_CTL_MODE_SWITCH_DELAY_SHIFT 17
#define CPRH_CTL_VOLTAGE_MULTIPLIER_MASK GENMASK(28, 25)
#define CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT 25
#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_MASK GENMASK(31, 29)
#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_SHIFT 29
#define CPRH_REG_STATUS(thread) \
((thread)->ctrl->cpr_hw_version >= CPRH_CPR_VERSION_4P5 \
? 0x3A84 + 0x4 * (thread)->thread_id : 0x3AA4)
#define CPRH_STATUS_CORNER GENMASK(5, 0)
#define CPRH_STATUS_CORNER_LAST_VOLT_MASK GENMASK(17, 6)
#define CPRH_STATUS_CORNER_LAST_VOLT_SHIFT 6
#define CPRH_REG_CORNER_BAND 0x3AA8
#define CPRH_CORNER_BAND_MASK GENMASK(5, 0)
#define CPRH_CORNER_BAND_SHIFT 6
#define CPRH_CORNER_BAND_MAX_COUNT 4
#define CPRH_MARGIN_TEMP_CORE_VBAND(core, vband) \
((vband) == 0 ? CPR4_REG_MARGIN_TEMP_CORE(core) \
: 0x3AB0 + 0x40 * ((vband) - 1) + 0x4 * (core))
#define CPRH_REG_MISC_REG2 0x3AAC
#define CPRH_MISC_REG2_ACD_ADJ_STEP_UP_LIMIT_MASK GENMASK(31, 29)
#define CPRH_MISC_REG2_ACD_ADJ_STEP_UP_LIMIT_SHIFT 29
#define CPRH_MISC_REG2_ACD_ADJ_STEP_DOWN_LIMIT_MASK GENMASK(28, 24)
#define CPRH_MISC_REG2_ACD_ADJ_STEP_DOWN_LIMIT_SHIFT 24
#define CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_UP_MASK GENMASK(23, 22)
#define CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_UP_SHIFT 22
#define CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_DOWN_MASK GENMASK(21, 20)
#define CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_DOWN_SHIFT 20
#define CPRH_MISC_REG2_ACD_NOTWAIT_4_CL_SETTLE_MASK BIT(16)
#define CPRH_MISC_REG2_ACD_NOTWAIT_4_CL_SETTLE_EN BIT(16)
#define CPRH_MISC_REG2_ACD_AVG_FAST_UPDATE_EN_MASK BIT(13)
#define CPRH_MISC_REG2_ACD_AVG_FAST_UPDATE_EN BIT(13)
#define CPRH_MISC_REG2_ACD_AVG_EN_MASK BIT(12)
#define CPRH_MISC_REG2_ACD_AVG_ENABLE BIT(12)
/* SAW module registers */
#define SAW_REG_AVS_CTL 0x904
#define SAW_REG_AVS_LIMIT 0x908
/*
* The amount of time to wait for the CPR controller to become idle when
* performing an aging measurement.
*/
#define CPR3_AGING_MEASUREMENT_TIMEOUT_NS 5000000
/*
* The number of individual aging measurements to perform which are then
* averaged together in order to determine the final aging adjustment value.
*/
#define CPR3_AGING_MEASUREMENT_ITERATIONS 16
/*
* Aging measurements for the aged and unaged ring oscillators take place a few
* microseconds apart. If the vdd-supply voltage fluctuates between the two
* measurements, then the difference between them will be incorrect. The
* difference could end up too high or too low. This constant defines the
* number of lowest and highest measurements to ignore when averaging.
*/
#define CPR3_AGING_MEASUREMENT_FILTER 3
/*
* The number of times to attempt the full aging measurement sequence before
* declaring a measurement failure.
*/
#define CPR3_AGING_RETRY_COUNT 5
/*
* The maximum time to wait in microseconds for a CPR register write to
* complete.
*/
#define CPR3_REGISTER_WRITE_DELAY_US 200
/*
* The number of times the CPRh controller multiplies the mode switch
* delay before utilizing it.
*/
#define CPRH_MODE_SWITCH_DELAY_FACTOR 4
/*
* The number of times the CPRh controller multiplies the delta quotient
* steps before utilizing it.
*/
#define CPRH_DELTA_QUOT_STEP_FACTOR 4
static DEFINE_MUTEX(cpr3_controller_list_mutex);
static LIST_HEAD(cpr3_controller_list);
static struct dentry *cpr3_debugfs_base;
/**
* cpr3_read() - read four bytes from the memory address specified
* @ctrl: Pointer to the CPR3 controller
* @offset: Offset in bytes from the CPR3 controller's base address
*
* Return: memory address value
*/
static inline u32 cpr3_read(struct cpr3_controller *ctrl, u32 offset)
{
if (!ctrl->cpr_enabled) {
cpr3_err(ctrl, "CPR register reads are not possible when CPR clocks are disabled\n");
return 0;
}
return readl_relaxed(ctrl->cpr_ctrl_base + offset);
}
/**
* cpr3_write() - write four bytes to the memory address specified
* @ctrl: Pointer to the CPR3 controller
* @offset: Offset in bytes from the CPR3 controller's base address
* @value: Value to write to the memory address
*
* Return: none
*/
static inline void cpr3_write(struct cpr3_controller *ctrl, u32 offset,
u32 value)
{
if (!ctrl->cpr_enabled) {
cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
return;
}
writel_relaxed(value, ctrl->cpr_ctrl_base + offset);
}
/**
* cpr3_masked_write() - perform a read-modify-write sequence so that only
* masked bits are modified
* @ctrl: Pointer to the CPR3 controller
* @offset: Offset in bytes from the CPR3 controller's base address
* @mask: Mask identifying the bits that should be modified
* @value: Value to write to the memory address
*
* Return: none
*/
static inline void cpr3_masked_write(struct cpr3_controller *ctrl, u32 offset,
u32 mask, u32 value)
{
u32 reg_val, orig_val;
if (!ctrl->cpr_enabled) {
cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
return;
}
reg_val = orig_val = readl_relaxed(ctrl->cpr_ctrl_base + offset);
reg_val &= ~mask;
reg_val |= value & mask;
if (reg_val != orig_val)
writel_relaxed(reg_val, ctrl->cpr_ctrl_base + offset);
}
/**
* cpr3_ctrl_loop_enable() - enable the CPR sensing loop for a given controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: none
*/
static inline void cpr3_ctrl_loop_enable(struct cpr3_controller *ctrl)
{
if (ctrl->cpr_enabled && !(ctrl->aggr_corner.sdelta
&& ctrl->aggr_corner.sdelta->allow_boost))
cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_ENABLE);
}
/**
* cpr3_ctrl_loop_disable() - disable the CPR sensing loop for a given
* controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: none
*/
static inline void cpr3_ctrl_loop_disable(struct cpr3_controller *ctrl)
{
if (ctrl->cpr_enabled)
cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_DISABLE);
}
/**
* cpr3_clock_enable() - prepare and enable all clocks used by this CPR3
* controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static int cpr3_clock_enable(struct cpr3_controller *ctrl)
{
int rc;
rc = clk_prepare_enable(ctrl->bus_clk);
if (rc) {
cpr3_err(ctrl, "failed to enable bus clock, rc=%d\n", rc);
return rc;
}
rc = clk_prepare_enable(ctrl->iface_clk);
if (rc) {
cpr3_err(ctrl, "failed to enable interface clock, rc=%d\n", rc);
clk_disable_unprepare(ctrl->bus_clk);
return rc;
}
rc = clk_prepare_enable(ctrl->core_clk);
if (rc) {
cpr3_err(ctrl, "failed to enable core clock, rc=%d\n", rc);
clk_disable_unprepare(ctrl->iface_clk);
clk_disable_unprepare(ctrl->bus_clk);
return rc;
}
return 0;
}
/**
* cpr3_clock_disable() - disable and unprepare all clocks used by this CPR3
* controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: none
*/
static void cpr3_clock_disable(struct cpr3_controller *ctrl)
{
clk_disable_unprepare(ctrl->core_clk);
clk_disable_unprepare(ctrl->iface_clk);
clk_disable_unprepare(ctrl->bus_clk);
}
/**
* cpr3_ctrl_clear_cpr4_config() - clear the CPR4 register configuration
* programmed for current aggregated corner of a given controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static inline int cpr3_ctrl_clear_cpr4_config(struct cpr3_controller *ctrl)
{
struct cpr4_sdelta *aggr_sdelta = ctrl->aggr_corner.sdelta;
bool cpr_enabled = ctrl->cpr_enabled;
int i, rc = 0;
if (!aggr_sdelta || !(aggr_sdelta->allow_core_count_adj
|| aggr_sdelta->allow_temp_adj || aggr_sdelta->allow_boost))
/* cpr4 features are not enabled */
return 0;
/* Ensure that CPR clocks are enabled before writing to registers. */
if (!cpr_enabled) {
rc = cpr3_clock_enable(ctrl);
if (rc) {
cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
return rc;
}
ctrl->cpr_enabled = true;
}
/*
* Clear feature enable configuration made for current
* aggregated corner.
*/
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
| CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_BOOST_EN
| CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK, 0);
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
0 << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
for (i = 0; i <= aggr_sdelta->max_core_count; i++) {
/* Clear voltage margin adjustments programmed in TEMP_COREi */
cpr3_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE(i), 0);
}
/* Turn off CPR clocks if they were off before this function call. */
if (!cpr_enabled) {
cpr3_clock_disable(ctrl);
ctrl->cpr_enabled = false;
}
return 0;
}
/**
* cpr3_closed_loop_enable() - enable logical CPR closed-loop operation
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static int cpr3_closed_loop_enable(struct cpr3_controller *ctrl)
{
int rc;
if (!ctrl->cpr_allowed_hw || !ctrl->cpr_allowed_sw) {
cpr3_err(ctrl, "cannot enable closed-loop CPR operation because it is disallowed\n");
return -EPERM;
} else if (ctrl->cpr_enabled) {
/* Already enabled */
return 0;
} else if (ctrl->cpr_suspended) {
/*
* CPR must remain disabled as the system is entering suspend.
*/
return 0;
}
rc = cpr3_clock_enable(ctrl);
if (rc) {
cpr3_err(ctrl, "unable to enable CPR clocks, rc=%d\n", rc);
return rc;
}
ctrl->cpr_enabled = true;
cpr3_debug(ctrl, "CPR closed-loop operation enabled\n");
return 0;
}
/**
* cpr3_closed_loop_disable() - disable logical CPR closed-loop operation
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static inline int cpr3_closed_loop_disable(struct cpr3_controller *ctrl)
{
if (!ctrl->cpr_enabled) {
/* Already disabled */
return 0;
}
cpr3_clock_disable(ctrl);
ctrl->cpr_enabled = false;
cpr3_debug(ctrl, "CPR closed-loop operation disabled\n");
return 0;
}
/**
* cpr3_regulator_get_gcnt() - returns the GCNT register value corresponding
* to the clock rate and sensor time of the CPR3 controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: GCNT value
*/
static u32 cpr3_regulator_get_gcnt(struct cpr3_controller *ctrl)
{
u64 temp;
unsigned int remainder;
u32 gcnt;
temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->sensor_time;
remainder = do_div(temp, 1000000000);
if (remainder)
temp++;
/*
* GCNT == 0 corresponds to a single ref clock measurement interval so
* offset GCNT values by 1.
*/
gcnt = temp - 1;
return gcnt;
}
/**
* cpr3_regulator_init_thread() - performs hardware initialization of CPR
* thread registers
* @thread: Pointer to the CPR3 thread
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_init_thread(struct cpr3_thread *thread)
{
u32 reg;
reg = (thread->consecutive_up << CPR3_THRESH_CONS_UP_SHIFT)
& CPR3_THRESH_CONS_UP_MASK;
reg |= (thread->consecutive_down << CPR3_THRESH_CONS_DOWN_SHIFT)
& CPR3_THRESH_CONS_DOWN_MASK;
reg |= (thread->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT)
& CPR3_THRESH_UP_THRESH_MASK;
reg |= (thread->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT)
& CPR3_THRESH_DOWN_THRESH_MASK;
cpr3_write(thread->ctrl, CPR3_REG_THRESH(thread->thread_id), reg);
/*
* Mask all RO's initially so that unused thread doesn't contribute
* to closed-loop voltage.
*/
cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
CPR3_RO_MASK);
return 0;
}
/**
* cpr4_regulator_init_temp_points() - performs hardware initialization of CPR4
* registers to track tsen temperature data and also specify the
* temperature band range values to apply different voltage margins
* @ctrl: Pointer to the CPR3 controller
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: 0 on success, errno on failure
*/
static int cpr4_regulator_init_temp_points(struct cpr3_controller *ctrl)
{
if (!ctrl->allow_temp_adj)
return 0;
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_TEMP_SENSOR_ID_START_MASK,
ctrl->temp_sensor_id_start
<< CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_TEMP_SENSOR_ID_END_MASK,
ctrl->temp_sensor_id_end
<< CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT2,
CPR4_MARGIN_TEMP_POINT2_MASK,
(ctrl->temp_band_count == 4 ? ctrl->temp_points[2] : 0x7FF)
<< CPR4_MARGIN_TEMP_POINT2_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
CPR4_MARGIN_TEMP_POINT1_MASK,
(ctrl->temp_band_count >= 3 ? ctrl->temp_points[1] : 0x7FF)
<< CPR4_MARGIN_TEMP_POINT1_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
CPR4_MARGIN_TEMP_POINT0_MASK,
(ctrl->temp_band_count >= 2 ? ctrl->temp_points[0] : 0x7FF)
<< CPR4_MARGIN_TEMP_POINT0_SHIFT);
return 0;
}
/**
* cpr3_regulator_init_cpr4() - performs hardware initialization at the
* controller and thread level required for CPR4 operation.
* @ctrl: Pointer to the CPR3 controller
*
* CPR interface/bus clocks must be enabled before calling this function.
* This function allocates sdelta structures and sdelta tables for aggregated
* corners of the controller and its threads.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
{
struct cpr3_thread *thread;
struct cpr3_regulator *vreg;
struct cpr4_sdelta *sdelta;
int i, j, ctrl_max_core_count, thread_max_core_count, rc = 0;
bool ctrl_valid_sdelta, thread_valid_sdelta;
u32 pmic_step_size = 1;
int thread_id = 0;
u64 temp;
if (ctrl->reset_step_quot_loop_en)
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_RESET_STEP_QUOT_LOOP_EN,
CPR4_MISC_RESET_STEP_QUOT_LOOP_EN);
if (ctrl->supports_hw_closed_loop) {
if (ctrl->saw_use_unit_mV)
pmic_step_size = ctrl->step_volt / 1000;
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
(pmic_step_size
<< CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
(ctrl->down_error_step_limit
<< CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
(ctrl->up_error_step_limit
<< CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
/*
* Enable thread aggregation regardless of which threads are
* enabled or disabled.
*/
cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
switch (ctrl->thread_count) {
case 0:
/* Disable both threads */
cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(0),
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(1),
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
break;
case 1:
/* Disable unused thread */
thread_id = ctrl->thread[0].thread_id ? 0 : 1;
cpr3_masked_write(ctrl,
CPR4_REG_CPR_MASK_THREAD(thread_id),
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
CPR4_CPR_MASK_THREAD_DISABLE_THREAD
| CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
break;
}
}
if (!ctrl->allow_core_count_adj && !ctrl->allow_temp_adj
&& !ctrl->allow_boost) {
/*
* Skip below configuration as none of the features
* are enabled.
*/
return rc;
}
if (ctrl->supports_hw_closed_loop)
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN,
CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK,
ctrl->step_quot_fixed
<< CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN,
(ctrl->use_dynamic_step_quot
? CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN : 0));
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK,
ctrl->initial_temp_band
<< CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT);
rc = cpr4_regulator_init_temp_points(ctrl);
if (rc) {
cpr3_err(ctrl, "initialize temp points failed, rc=%d\n", rc);
return rc;
}
if (ctrl->voltage_settling_time) {
/*
* Configure the settling timer used to account for
* one VDD supply step.
*/
temp = (u64)ctrl->cpr_clock_rate
* (u64)ctrl->voltage_settling_time;
do_div(temp, 1000000000);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
temp
<< CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
}
/*
* Allocate memory for cpr4_sdelta structure and sdelta table for
* controller aggregated corner by finding the maximum core count
* used by any cpr3 regulators.
*/
ctrl_max_core_count = 1;
ctrl_valid_sdelta = false;
for (i = 0; i < ctrl->thread_count; i++) {
thread = &ctrl->thread[i];
/*
* Allocate memory for cpr4_sdelta structure and sdelta table
* for thread aggregated corner by finding the maximum core
* count used by any cpr3 regulators of the thread.
*/
thread_max_core_count = 1;
thread_valid_sdelta = false;
for (j = 0; j < thread->vreg_count; j++) {
vreg = &thread->vreg[j];
thread_max_core_count = max(thread_max_core_count,
vreg->max_core_count);
thread_valid_sdelta |= (vreg->allow_core_count_adj
| vreg->allow_temp_adj
| vreg->allow_boost);
}
if (thread_valid_sdelta) {
sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta),
GFP_KERNEL);
if (!sdelta)
return -ENOMEM;
sdelta->table = devm_kcalloc(ctrl->dev,
thread_max_core_count
* ctrl->temp_band_count,
sizeof(*sdelta->table),
GFP_KERNEL);
if (!sdelta->table)
return -ENOMEM;
sdelta->boost_table = devm_kcalloc(ctrl->dev,
ctrl->temp_band_count,
sizeof(*sdelta->boost_table),
GFP_KERNEL);
if (!sdelta->boost_table)
return -ENOMEM;
thread->aggr_corner.sdelta = sdelta;
}
ctrl_valid_sdelta |= thread_valid_sdelta;
ctrl_max_core_count = max(ctrl_max_core_count,
thread_max_core_count);
}
if (ctrl_valid_sdelta) {
sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta), GFP_KERNEL);
if (!sdelta)
return -ENOMEM;
sdelta->table = devm_kcalloc(ctrl->dev, ctrl_max_core_count
* ctrl->temp_band_count,
sizeof(*sdelta->table), GFP_KERNEL);
if (!sdelta->table)
return -ENOMEM;
sdelta->boost_table = devm_kcalloc(ctrl->dev,
ctrl->temp_band_count,
sizeof(*sdelta->boost_table),
GFP_KERNEL);
if (!sdelta->boost_table)
return -ENOMEM;
ctrl->aggr_corner.sdelta = sdelta;
}
return 0;
}
/**
* cpr3_write_temp_core_margin() - programs hardware SDELTA registers with
* the voltage margin adjustments that need to be applied for
* different online core-count and temperature bands.
* @ctrl: Pointer to the CPR3 controller
* @addr: SDELTA register address
* @temp_core_adj: Array of voltage margin values for different temperature
* bands.
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: none
*/
static void cpr3_write_temp_core_margin(struct cpr3_controller *ctrl,
int addr, int *temp_core_adj)
{
int i, margin_steps;
u32 reg = 0;
for (i = 0; i < ctrl->temp_band_count; i++) {
margin_steps = max(min(temp_core_adj[i], 127), -128);
reg |= (margin_steps & CPR4_MARGIN_TEMP_CORE_ADJ_MASK) <<
(i * CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT);
}
cpr3_write(ctrl, addr, reg);
cpr3_debug(ctrl, "sdelta offset=0x%08x, val=0x%08x\n", addr, reg);
}
/**
* cpr3_controller_program_sdelta() - programs hardware SDELTA registers with
* the voltage margin adjustments that need to be applied at
* different online core-count and temperature bands. Also,
* programs hardware register configuration for per-online-core
* and per-temperature based adjustments.
* @ctrl: Pointer to the CPR3 controller
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
{
struct cpr3_corner *corner = &ctrl->aggr_corner;
struct cpr4_sdelta *sdelta = corner->sdelta;
int i, index, max_core_count, rc = 0;
bool cpr_enabled = ctrl->cpr_enabled;
if (!sdelta)
/* cpr4_sdelta not defined for current aggregated corner */
return 0;
if (ctrl->supports_hw_closed_loop && ctrl->cpr_enabled) {
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
(ctrl->use_hw_closed_loop && !sdelta->allow_boost)
? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0);
}
if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj
&& !sdelta->allow_boost) {
/*
* Per-online-core, per-temperature and voltage boost
* adjustments are disabled for this aggregation corner.
*/
return 0;
}
/* Ensure that CPR clocks are enabled before writing to registers. */
if (!cpr_enabled) {
rc = cpr3_clock_enable(ctrl);
if (rc) {
cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
return rc;
}
ctrl->cpr_enabled = true;
}
max_core_count = sdelta->max_core_count;
if (sdelta->allow_core_count_adj || sdelta->allow_temp_adj) {
if (sdelta->allow_core_count_adj) {
/* Program TEMP_CORE0 to same margins as TEMP_CORE1 */
cpr3_write_temp_core_margin(ctrl,
CPR4_REG_MARGIN_TEMP_CORE(0),
&sdelta->table[0]);
}
for (i = 0; i < max_core_count; i++) {
index = i * sdelta->temp_band_count;
/*
* Program TEMP_COREi with voltage margin adjustments
* that need to be applied when the number of cores
* becomes i.
*/
cpr3_write_temp_core_margin(ctrl,
CPR4_REG_MARGIN_TEMP_CORE(
sdelta->allow_core_count_adj
? i + 1 : max_core_count),
&sdelta->table[index]);
}
}
if (sdelta->allow_boost) {
/* Program only boost_num_cores row of SDELTA */
cpr3_write_temp_core_margin(ctrl,
CPR4_REG_MARGIN_TEMP_CORE(sdelta->boost_num_cores),
&sdelta->boost_table[0]);
}
if (!sdelta->allow_core_count_adj && !sdelta->allow_boost) {
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
max_core_count
<< CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
}
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
| CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_BOOST_EN,
max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
| ((sdelta->allow_core_count_adj || sdelta->allow_boost)
? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
| ((sdelta->allow_temp_adj && ctrl->supports_hw_closed_loop)
? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
| (((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
|| !ctrl->supports_hw_closed_loop)
? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
| (sdelta->allow_boost
? CPR4_MARGIN_ADJ_CTL_BOOST_EN : 0));
/*
* Ensure that all previous CPR register writes have completed before
* continuing.
*/
mb();
/* Turn off CPR clocks if they were off before this function call. */
if (!cpr_enabled) {
cpr3_clock_disable(ctrl);
ctrl->cpr_enabled = false;
}
return 0;
}
/**
* cpr3_regulator_set_base_target_quot() - configure the target quotient
* for each RO of the CPR3 regulator for CPRh operation.
* In particular, the quotient of the RO selected for operation
* should correspond to the lowest target quotient across the
* corners supported by the single regulator of the CPR3 thread.
* @vreg: Pointer to the CPR3 regulator
* @base_quots: Pointer to the base quotient array. The array must be
* of size CPR3_RO_COUNT and it is populated with the
* base quotient per-RO.
*
* Return: none
*/
static void cpr3_regulator_set_base_target_quot(struct cpr3_regulator *vreg,
u32 *base_quots)
{
struct cpr3_controller *ctrl = vreg->thread->ctrl;
int i, j, ro_mask = CPR3_RO_MASK;
u32 min_quot;
for (i = 0; i < vreg->corner_count; i++)
ro_mask &= vreg->corner[i].ro_mask;
/* Unmask the ROs selected for active use. */
cpr3_write(ctrl, CPR3_REG_RO_MASK(vreg->thread->thread_id),
ro_mask);
for (i = 0; i < CPR3_RO_COUNT; i++) {
for (j = 0, min_quot = INT_MAX; j < vreg->corner_count; j++)
if (vreg->corner[j].target_quot[i])
min_quot = min(min_quot,
vreg->corner[j].target_quot[i]);
if (min_quot == INT_MAX)
min_quot = 0;
cpr3_write(ctrl,
CPR3_REG_TARGET_QUOT(vreg->thread->thread_id, i),
min_quot);
base_quots[i] = min_quot;
}
}
/**
* cpr3_regulator_init_cprh_corners() - configure the per-corner CPRh registers
* @vreg: Pointer to the CPR3 regulator
*
* This function programs the controller registers which contain all information
* necessary to resolve the closed-loop voltage per-corner at runtime such as
* open-loop and floor voltages, target quotient delta, and RO select value.
* These registers also provide a means to disable closed-loop operation, core
* and temperature adjustments.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_init_cprh_corners(struct cpr3_regulator *vreg)
{
struct cpr3_controller *ctrl = vreg->thread->ctrl;
struct cpr3_corner *corner;
u32 reg, delta_quot_steps, ro_sel;
u32 *base_quots;
int open_loop_volt_steps, floor_volt_steps, i, j, rc = 0;
base_quots = kcalloc(CPR3_RO_COUNT, sizeof(*base_quots),
GFP_KERNEL);
if (!base_quots)
return -ENOMEM;
cpr3_regulator_set_base_target_quot(vreg, base_quots);
for (i = 0; i < vreg->corner_count; i++) {
corner = &vreg->corner[i];
for (j = 0, ro_sel = INT_MAX; j < CPR3_RO_COUNT; j++) {
if (corner->target_quot[j]) {
ro_sel = j;
break;
}
}
if (ro_sel == INT_MAX) {
if (!corner->proc_freq) {
/*
* Corner is not used as active DCVS set point
* select RO 0 arbitrarily.
*/
ro_sel = 0;
} else if (ctrl->ignore_invalid_fuses) {
/*
* Fuses are not initialized on some simulator
* targets. Select RO 0 arbitrarily.
*/
cpr3_debug(vreg, "ignored that corner=%d has invalid RO select value\n",
i);
ro_sel = 0;
} else {
cpr3_err(vreg, "corner=%d has invalid RO select value\n",
i);
rc = -EINVAL;
goto free_base_quots;
}
}
open_loop_volt_steps = DIV_ROUND_UP(corner->open_loop_volt -
ctrl->base_volt,
ctrl->step_volt);
floor_volt_steps = DIV_ROUND_UP(corner->floor_volt -
ctrl->base_volt,
ctrl->step_volt);
delta_quot_steps = corner->proc_freq ?
DIV_ROUND_UP(corner->target_quot[ro_sel] -
base_quots[ro_sel],
CPRH_DELTA_QUOT_STEP_FACTOR) :
0;
if (open_loop_volt_steps > CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE ||
floor_volt_steps > CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE ||
delta_quot_steps > CPRH_CORNER_QUOT_DELTA_MAX_VALUE) {
cpr3_err(ctrl, "invalid CPRh corner configuration: open_loop_volt_steps=%d (%d max.), floor_volt_steps=%d (%d max), delta_quot_steps=%d (%d max)\n",
open_loop_volt_steps,
CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE,
floor_volt_steps,
CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE,
delta_quot_steps,
CPRH_CORNER_QUOT_DELTA_MAX_VALUE);
rc = -EINVAL;
goto free_base_quots;
}
reg = (open_loop_volt_steps << CPRH_CORNER_INIT_VOLTAGE_SHIFT)
& CPRH_CORNER_INIT_VOLTAGE_MASK;
reg |= (floor_volt_steps << CPRH_CORNER_FLOOR_VOLTAGE_SHIFT)
& CPRH_CORNER_FLOOR_VOLTAGE_MASK;
reg |= (delta_quot_steps << CPRH_CORNER_QUOT_DELTA_SHIFT)
& CPRH_CORNER_QUOT_DELTA_MASK;
reg |= (ro_sel << CPRH_CORNER_RO_SEL_SHIFT)
& CPRH_CORNER_RO_SEL_MASK;
if (corner->use_open_loop)
reg |= CPRH_CORNER_CPR_CL_DISABLE;
cpr3_debug(ctrl, "corner=%d open_loop_volt_steps=%d, floor_volt_steps=%d, delta_quot_steps=%d, base_volt=%d, step_volt=%d, base_quot=%d\n",
i, open_loop_volt_steps, floor_volt_steps,
delta_quot_steps, ctrl->base_volt,
ctrl->step_volt, base_quots[ro_sel]);
cpr3_write(ctrl, CPRH_REG_CORNER(vreg->thread, i), reg);
}
free_base_quots:
kfree(base_quots);
return rc;
}
/**
* cprh_controller_program_sdelta() - programs hardware SDELTA registers with
* the margins that need to be applied at different online
* core-count and temperature bands for each corner band. Also,
* programs hardware register configuration for core-count and
* temp-based adjustments
*
* @ctrl: Pointer to the CPR3 controller
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: none
*/
static void cprh_controller_program_sdelta(
struct cpr3_controller *ctrl)
{
/* Only thread 0 supports sdelta */
struct cpr3_regulator *vreg = &ctrl->thread[0].vreg[0];
struct cprh_corner_band *corner_band;
struct cpr4_sdelta *sdelta;
int i, j, index;
u32 reg = 0;
if (!vreg->allow_core_count_adj && !vreg->allow_temp_adj)
return;
if (vreg->thread->thread_id != 0) {
cpr3_err(vreg, "core count and temperature based adjustments are only allowed for CPR thread 0\n");
return;
}
cpr4_regulator_init_temp_points(ctrl);
for (i = 0; i < CPRH_CORNER_BAND_MAX_COUNT; i++) {
reg |= (i < vreg->corner_band_count ?
vreg->corner_band[i].corner
& CPRH_CORNER_BAND_MASK :
vreg->corner_count + 1)
<< (i * CPRH_CORNER_BAND_SHIFT);
}
cpr3_write(ctrl, CPRH_REG_CORNER_BAND, reg);
for (i = 0; i < vreg->corner_band_count; i++) {
corner_band = &vreg->corner_band[i];
sdelta = corner_band->sdelta;
if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
/*
* Per-online-core and per-temperature margin
* adjustments are disabled for this corner band.
*/
continue;
}
if (vreg->allow_core_count_adj)
cpr3_write_temp_core_margin(ctrl,
CPRH_MARGIN_TEMP_CORE_VBAND(0, i),
&sdelta->table[0]);
for (j = 0; j < sdelta->max_core_count; j++) {
index = j * sdelta->temp_band_count;
cpr3_write_temp_core_margin(ctrl,
CPRH_MARGIN_TEMP_CORE_VBAND(
sdelta->allow_core_count_adj
? j + 1 : vreg->max_core_count, i),
&sdelta->table[index]);
}
}
if (!vreg->allow_core_count_adj) {
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
vreg->max_core_count
<< CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
}
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
| CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
| CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
vreg->max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
| ((vreg->allow_core_count_adj)
? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
| (vreg->allow_temp_adj ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
| ((ctrl->use_hw_closed_loop)
? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
| (ctrl->use_hw_closed_loop
? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0));
/* Ensure that previous CPR register writes complete */
mb();
}
static int cprh_regulator_aging_adjust(struct cpr3_controller *ctrl);
/**
* cpr3_regulator_cprh_initialized() - checks if CPRh has already been
* initialized by the boot loader
* @ctrl: Pointer to the CPR3 controller
*
* Return: true if CPRh controller is already initialized else false
*/
static bool cpr3_regulator_cprh_initialized(struct cpr3_controller *ctrl)
{
u32 reg;
if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
return false;
ctrl->cpr_hw_version = readl_relaxed(ctrl->cpr_ctrl_base
+ CPR3_REG_CPR_VERSION);
reg = readl_relaxed(ctrl->cpr_ctrl_base + CPRH_REG_CTL(ctrl));
return reg & CPRH_CTL_OSM_ENABLED;
}
/**
* cpr3_regulator_init_cprh() - performs hardware initialization at the
* controller and thread level required for CPRh operation.
* @ctrl: Pointer to the CPR3 controller
*
* CPR interface/bus clocks must be enabled before calling this function.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_init_cprh(struct cpr3_controller *ctrl)
{
u32 reg, pmic_step_size = 1;
u64 temp;
int i, rc;
/* One or two threads each with a single regulator supported */
if (ctrl->thread_count < 1 || ctrl->thread_count > 2) {
cpr3_err(ctrl, "expected 1 or 2 threads but found %d\n",
ctrl->thread_count);
return -EINVAL;
} else if (ctrl->thread[0].vreg_count != 1) {
cpr3_err(ctrl, "expected 1 regulator for thread 0 but found %d\n",
ctrl->thread[0].vreg_count);
return -EINVAL;
} else if (ctrl->thread_count == 2 && ctrl->thread[1].vreg_count != 1) {
cpr3_err(ctrl, "expected 1 regulator for thread 1 but found %d\n",
ctrl->thread[1].vreg_count);
return -EINVAL;
}
rc = cprh_regulator_aging_adjust(ctrl);
if (rc && rc != -ETIMEDOUT) {
/*
* Don't fail initialization if the CPR aging measurement
* timed out due to sensors not being available.
*/
cpr3_err(ctrl, "CPR aging adjustment failed, rc=%d\n", rc);
return rc;
}
cprh_controller_program_sdelta(ctrl);
for (i = 0; i < ctrl->thread_count; i++) {
rc = cpr3_regulator_init_cprh_corners(&ctrl->thread[i].vreg[0]);
if (rc) {
cpr3_err(ctrl, "failed to initialize CPRh corner registers\n");
return rc;
}
}
if (ctrl->reset_step_quot_loop_en)
cpr3_masked_write(ctrl, CPR4_REG_MISC,
CPR4_MISC_RESET_STEP_QUOT_LOOP_EN,
CPR4_MISC_RESET_STEP_QUOT_LOOP_EN);
if (ctrl->saw_use_unit_mV)
pmic_step_size = ctrl->step_volt / 1000;
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
(pmic_step_size
<< CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
(ctrl->down_error_step_limit
<< CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
(ctrl->up_error_step_limit
<< CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK,
ctrl->step_quot_fixed
<< CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN,
(ctrl->use_dynamic_step_quot
? CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN : 0));
if (ctrl->thread_count > 1)
cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
if (ctrl->voltage_settling_time) {
/*
* Configure the settling timer used to account for
* one VDD supply step.
*/
temp = (u64)ctrl->cpr_clock_rate
* (u64)ctrl->voltage_settling_time;
do_div(temp, 1000000000);
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
temp
<< CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
}
if (ctrl->corner_switch_delay_time) {
/*
* Configure the settling timer used to delay
* following SAW requests
*/
temp = (u64)ctrl->cpr_clock_rate
* (u64)ctrl->corner_switch_delay_time;
do_div(temp, 1000000000);
do_div(temp, CPRH_MODE_SWITCH_DELAY_FACTOR);
cpr3_masked_write(ctrl, CPRH_REG_CTL(ctrl),
CPRH_CTL_MODE_SWITCH_DELAY_MASK,
temp << CPRH_CTL_MODE_SWITCH_DELAY_SHIFT);
}
/*
* Configure CPRh ACD AVG registers on controllers
* that support this feature.
*/
if (ctrl->cpr_hw_version >= CPRH_CPR_VERSION_4P5
&& ctrl->acd_avg_enabled) {
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_ADJ_STEP_UP_LIMIT_MASK,
ctrl->acd_adj_up_step_limit <<
CPRH_MISC_REG2_ACD_ADJ_STEP_UP_LIMIT_SHIFT);
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_ADJ_STEP_DOWN_LIMIT_MASK,
ctrl->acd_adj_down_step_limit <<
CPRH_MISC_REG2_ACD_ADJ_STEP_DOWN_LIMIT_SHIFT);
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_UP_MASK,
ctrl->acd_adj_up_step_size <<
CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_UP_SHIFT);
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_DOWN_MASK,
ctrl->acd_adj_down_step_size <<
CPRH_MISC_REG2_ACD_ADJ_STEP_SIZE_DOWN_SHIFT);
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_NOTWAIT_4_CL_SETTLE_MASK,
(ctrl->acd_notwait_for_cl_settled
? CPRH_MISC_REG2_ACD_NOTWAIT_4_CL_SETTLE_EN
: 0));
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_AVG_FAST_UPDATE_EN_MASK,
(ctrl->acd_adj_avg_fast_update
? CPRH_MISC_REG2_ACD_AVG_FAST_UPDATE_EN
: 0));
cpr3_masked_write(ctrl, CPRH_REG_MISC_REG2,
CPRH_MISC_REG2_ACD_AVG_EN_MASK,
CPRH_MISC_REG2_ACD_AVG_ENABLE);
}
/*
* Program base voltage and voltage multiplier values which
* are used for floor and initial voltage calculations by the
* CPRh controller.
*/
reg = (DIV_ROUND_UP(ctrl->base_volt, ctrl->step_volt)
<< CPRH_CTL_BASE_VOLTAGE_SHIFT)
& CPRH_CTL_BASE_VOLTAGE_MASK;
reg |= (DIV_ROUND_UP(ctrl->step_volt, 1000)
<< CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT)
& CPRH_CTL_VOLTAGE_MULTIPLIER_MASK;
/* Enable OSM block interface with CPR */
reg |= CPRH_CTL_OSM_ENABLED;
cpr3_masked_write(ctrl, CPRH_REG_CTL(ctrl), CPRH_CTL_BASE_VOLTAGE_MASK
| CPRH_CTL_VOLTAGE_MULTIPLIER_MASK
| CPRH_CTL_OSM_ENABLED, reg);
/* Enable loop_en */
cpr3_ctrl_loop_enable(ctrl);
return 0;
}
/**
* cpr3_regulator_init_ctrl() - performs hardware initialization of CPR
* controller registers
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
{
int i, j, k, m, rc;
u32 ro_used = 0;
u32 gcnt, cont_dly, up_down_dly, val;
u64 temp;
char *mode;
if (ctrl->core_clk) {
rc = clk_set_rate(ctrl->core_clk, ctrl->cpr_clock_rate);
if (rc) {
cpr3_err(ctrl, "clk_set_rate(core_clk, %u) failed, rc=%d\n",
ctrl->cpr_clock_rate, rc);
return rc;
}
}
rc = cpr3_clock_enable(ctrl);
if (rc) {
cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
return rc;
}
ctrl->cpr_enabled = true;
ctrl->cpr_hw_version = cpr3_read(ctrl, CPR3_REG_CPR_VERSION);
/* Find all RO's used by any corner of any regulator. */
for (i = 0; i < ctrl->thread_count; i++)
for (j = 0; j < ctrl->thread[i].vreg_count; j++)
for (k = 0; k < ctrl->thread[i].vreg[j].corner_count;
k++)
for (m = 0; m < CPR3_RO_COUNT; m++)
if (ctrl->thread[i].vreg[j].corner[k].
target_quot[m])
ro_used |= BIT(m);
/* Configure the GCNT of the RO's that will be used */
gcnt = cpr3_regulator_get_gcnt(ctrl);
for (i = 0; i < CPR3_RO_COUNT; i++)
if (ro_used & BIT(i))
cpr3_write(ctrl, CPR3_REG_GCNT(i), gcnt);
/* Configure the loop delay time */
temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->loop_time;
do_div(temp, 1000000000);
cont_dly = temp;
if (ctrl->supports_hw_closed_loop
&& ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly);
else
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, cont_dly);
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
temp = (u64)ctrl->cpr_clock_rate *
(u64)ctrl->up_down_delay_time;
do_div(temp, 1000000000);
up_down_dly = temp;
if (ctrl->supports_hw_closed_loop)
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
up_down_dly);
cpr3_debug(ctrl, "up_down_dly=%u, up_down_delay_time=%u ns\n",
up_down_dly, ctrl->up_down_delay_time);
}
cpr3_debug(ctrl, "cpr_clock_rate=%u HZ, sensor_time=%u ns, loop_time=%u ns, gcnt=%u, cont_dly=%u\n",
ctrl->cpr_clock_rate, ctrl->sensor_time, ctrl->loop_time,
gcnt, cont_dly);
/* Configure CPR sensor operation */
val = (ctrl->idle_clocks << CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT)
& CPR3_CPR_CTL_IDLE_CLOCKS_MASK;
val |= (ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
& CPR3_CPR_CTL_COUNT_MODE_MASK;
val |= (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT)
& CPR3_CPR_CTL_COUNT_REPEAT_MASK;
cpr3_write(ctrl, CPR3_REG_CPR_CTL, val);
cpr3_debug(ctrl, "idle_clocks=%u, count_mode=%u, count_repeat=%u; CPR_CTL=0x%08X\n",
ctrl->idle_clocks, ctrl->count_mode, ctrl->count_repeat, val);
/* Configure CPR default step quotients */
val = (ctrl->step_quot_init_min << CPR3_CPR_STEP_QUOT_MIN_SHIFT)
& CPR3_CPR_STEP_QUOT_MIN_MASK;
val |= (ctrl->step_quot_init_max << CPR3_CPR_STEP_QUOT_MAX_SHIFT)
& CPR3_CPR_STEP_QUOT_MAX_MASK;
cpr3_write(ctrl, CPR3_REG_CPR_STEP_QUOT, val);
cpr3_debug(ctrl, "step_quot_min=%u, step_quot_max=%u; STEP_QUOT=0x%08X\n",
ctrl->step_quot_init_min, ctrl->step_quot_init_max, val);
/* Configure the CPR sensor ownership */
for (i = 0; i < ctrl->sensor_count; i++)
cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(i),
ctrl->sensor_owner[i]);
/* Configure per-thread registers */
for (i = 0; i < ctrl->thread_count; i++) {
rc = cpr3_regulator_init_thread(&ctrl->thread[i]);
if (rc) {
cpr3_err(ctrl, "CPR thread register initialization failed, rc=%d\n",
rc);
return rc;
}
}
if (ctrl->supports_hw_closed_loop) {
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4 ||
ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
ctrl->use_hw_closed_loop
? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
: CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
} else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
ctrl->use_hw_closed_loop
? CPR3_HW_CLOSED_LOOP_ENABLE
: CPR3_HW_CLOSED_LOOP_DISABLE);
cpr3_debug(ctrl, "PD_THROTTLE=0x%08X\n",
ctrl->proc_clock_throttle);
}
if ((ctrl->use_hw_closed_loop ||
ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
rc = regulator_enable(ctrl->vdd_limit_regulator);
if (rc) {
cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
rc);
return rc;
}
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
rc = msm_spm_avs_enable_irq(0,
MSM_SPM_AVS_IRQ_MAX);
if (rc) {
cpr3_err(ctrl, "could not enable max IRQ, rc=%d\n",
rc);
return rc;
}
}
}
}
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
rc = cpr3_regulator_init_cpr4(ctrl);
if (rc) {
cpr3_err(ctrl, "CPR4-specific controller initialization failed, rc=%d\n",
rc);
return rc;
}
} else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
rc = cpr3_regulator_init_cprh(ctrl);
if (rc) {
cpr3_err(ctrl, "CPRh-specific controller initialization failed, rc=%d\n",
rc);
return rc;
}
}
/* Ensure that all register writes complete before disabling clocks. */
wmb();
/* Keep CPR clocks on for CPRh full HW closed-loop operation */
if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
cpr3_clock_disable(ctrl);
ctrl->cpr_enabled = false;
}
if (!ctrl->cpr_allowed_sw || !ctrl->cpr_allowed_hw)
mode = "open-loop";
else if (ctrl->supports_hw_closed_loop &&
ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
mode = ctrl->use_hw_closed_loop
? "HW closed-loop" : "SW closed-loop";
else if (ctrl->supports_hw_closed_loop &&
ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
mode = ctrl->use_hw_closed_loop
? "full HW closed-loop" : "open-loop";
else
mode = "closed-loop";
cpr3_info(ctrl, "Default CPR mode = %s", mode);
return 0;
}
/**
* cpr3_regulator_init_hw_closed_loop_dependencies() - perform hardware
* initialization steps to ensure that CPR HW closed-loop voltage
* change requests are able to reach the PMIC regulator
* @pdev: Platform device pointer for the CPR3 controller
* @ctrl: Pointer to the CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static int
cpr3_regulator_init_hw_closed_loop_dependencies(struct platform_device *pdev,
struct cpr3_controller *ctrl)
{
struct resource *res;
int rc = 0;
u32 val;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "saw");
if (res && res->start)
ctrl->saw_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (ctrl->saw_base) {
/* Configure SAW registers directly. */
rc = of_property_read_u32(ctrl->dev->of_node,
"qcom,saw-avs-ctrl", &val);
if (rc) {
cpr3_err(ctrl, "unable to read DT property qcom,saw-avs-ctrl, rc=%d\n",
rc);
return rc;
}
writel_relaxed(val, ctrl->saw_base + SAW_REG_AVS_CTL);
rc = of_property_read_u32(ctrl->dev->of_node,
"qcom,saw-avs-limit", &val);
if (rc) {
cpr3_err(ctrl, "unable to read DT property qcom,saw-avs-limit, rc=%d\n",
rc);
return rc;
}
writel_relaxed(val, ctrl->saw_base + SAW_REG_AVS_LIMIT);
} else {
/* Wait for SPM driver to configure SAW registers. */
rc = msm_spm_probe_done();
if (rc) {
if (rc != -EPROBE_DEFER)
cpr3_err(ctrl, "spm unavailable, rc=%d\n", rc);
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_set_target_quot() - configure the target quotient for each
* RO of the CPR3 thread and set the RO mask
* @thread: Pointer to the CPR3 thread
*
* Return: none
*/
static void cpr3_regulator_set_target_quot(struct cpr3_thread *thread)
{
u32 new_quot, last_quot;
int i;
if (thread->aggr_corner.ro_mask == CPR3_RO_MASK
&& thread->last_closed_loop_aggr_corner.ro_mask == CPR3_RO_MASK) {
/* Avoid writing target quotients since all RO's are masked. */
return;
} else if (thread->aggr_corner.ro_mask == CPR3_RO_MASK) {
cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
CPR3_RO_MASK);
thread->last_closed_loop_aggr_corner.ro_mask = CPR3_RO_MASK;
/*
* Only the RO_MASK register needs to be written since all
* RO's are masked.
*/
return;
} else if (thread->aggr_corner.ro_mask
!= thread->last_closed_loop_aggr_corner.ro_mask) {
cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
thread->aggr_corner.ro_mask);
}
for (i = 0; i < CPR3_RO_COUNT; i++) {
new_quot = thread->aggr_corner.target_quot[i];
last_quot = thread->last_closed_loop_aggr_corner.target_quot[i];
if (new_quot != last_quot)
cpr3_write(thread->ctrl,
CPR3_REG_TARGET_QUOT(thread->thread_id, i),
new_quot);
}
thread->last_closed_loop_aggr_corner = thread->aggr_corner;
}
/**
* cpr3_update_vreg_closed_loop_volt() - update the last known settled
* closed loop voltage for a CPR3 regulator
* @vreg: Pointer to the CPR3 regulator
* @vdd_volt: Last known settled voltage in microvolts for the
* VDD supply
* @reg_last_measurement: Value read from the LAST_MEASUREMENT register
*
* Return: none
*/
static void cpr3_update_vreg_closed_loop_volt(struct cpr3_regulator *vreg,
int vdd_volt, u32 reg_last_measurement)
{
bool step_dn, step_up, aggr_step_up, aggr_step_dn, aggr_step_mid;
bool valid, pd_valid, saw_error;
struct cpr3_controller *ctrl = vreg->thread->ctrl;
struct cpr3_corner *corner;
u32 id;
if (vreg->last_closed_loop_corner == CPR3_REGULATOR_CORNER_INVALID)
return;
corner = &vreg->corner[vreg->last_closed_loop_corner];
if (vreg->thread->last_closed_loop_aggr_corner.ro_mask
== CPR3_RO_MASK || !vreg->aggregated) {
return;
} else if (!ctrl->cpr_enabled || !ctrl->last_corner_was_closed_loop) {
return;
} else if (ctrl->thread_count == 1
&& vdd_volt >= corner->floor_volt
&& vdd_volt <= corner->ceiling_volt) {
corner->last_volt = vdd_volt;
cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
vreg->last_closed_loop_corner, corner->last_volt,
vreg->last_closed_loop_corner,
corner->ceiling_volt,
vreg->last_closed_loop_corner,
corner->floor_volt);
return;
} else if (!ctrl->supports_hw_closed_loop) {
return;
} else if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPR3) {
corner->last_volt = vdd_volt;
cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
vreg->last_closed_loop_corner, corner->last_volt,
vreg->last_closed_loop_corner,
corner->ceiling_volt,
vreg->last_closed_loop_corner,
corner->floor_volt);
return;
}
/* CPR clocks are on and HW closed loop is supported */
valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
if (!valid) {
cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X valid bit not set\n",
reg_last_measurement);
return;
}
id = vreg->thread->thread_id;
step_dn
= !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_DN(id));
step_up
= !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_UP(id));
aggr_step_dn = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_DN);
aggr_step_mid
= !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_MID);
aggr_step_up = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_UP);
saw_error = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_SAW_ERROR);
pd_valid
= !((((reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
>> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT)
& vreg->pd_bypass_mask) == vreg->pd_bypass_mask);
if (!pd_valid) {
cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X, all power domains bypassed\n",
reg_last_measurement);
return;
} else if (step_dn && step_up) {
cpr3_err(vreg, "both up and down status bits set, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
reg_last_measurement);
return;
} else if (aggr_step_dn && step_dn && vdd_volt < corner->last_volt
&& vdd_volt >= corner->floor_volt) {
corner->last_volt = vdd_volt;
} else if (aggr_step_up && step_up && vdd_volt > corner->last_volt
&& vdd_volt <= corner->ceiling_volt) {
corner->last_volt = vdd_volt;
} else if (aggr_step_mid
&& vdd_volt >= corner->floor_volt
&& vdd_volt <= corner->ceiling_volt) {
corner->last_volt = vdd_volt;
} else if (saw_error && (vdd_volt == corner->ceiling_volt
|| vdd_volt == corner->floor_volt)) {
corner->last_volt = vdd_volt;
} else {
cpr3_debug(vreg, "last_volt not updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, vdd_volt=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
vreg->last_closed_loop_corner, corner->last_volt,
vreg->last_closed_loop_corner,
corner->ceiling_volt,
vreg->last_closed_loop_corner, corner->floor_volt,
vdd_volt, reg_last_measurement);
return;
}
cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
vreg->last_closed_loop_corner, corner->last_volt,
vreg->last_closed_loop_corner, corner->ceiling_volt,
vreg->last_closed_loop_corner, corner->floor_volt,
reg_last_measurement);
}
/**
* cpr3_regulator_config_ldo_retention() - configure per-regulator LDO retention
* mode
* @vreg: Pointer to the CPR3 regulator to configure
* @ref_volt: Reference voltage used to determine if LDO retention
* mode can be allowed. It corresponds either to the
* aggregated floor voltage or the next VDD supply setpoint
*
* This function determines if a CPR3 regulator's configuration satisfies safe
* operating voltages for LDO retention and uses the regulator_allow_bypass()
* interface on the LDO retention regulator to enable or disable such feature
* accordingly.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_ldo_retention(struct cpr3_regulator *vreg,
int ref_volt)
{
struct regulator *ldo_ret_reg = vreg->ldo_ret_regulator;
int retention_volt, rc;
enum msm_ldo_supply_mode mode;
if (!ldo_ret_reg) {
/* LDO retention regulator is not defined */
return 0;
}
retention_volt = regulator_get_voltage(ldo_ret_reg);
if (retention_volt < 0) {
cpr3_err(vreg, "regulator_get_voltage(ldo_ret) failed, rc=%d\n",
retention_volt);
return retention_volt;
}
mode = ref_volt >= retention_volt + vreg->ldo_min_headroom_volt
? LDO_MODE : BHS_MODE;
rc = regulator_allow_bypass(ldo_ret_reg, mode);
if (rc)
cpr3_err(vreg, "regulator_allow_bypass(ldo_ret) == %s failed, rc=%d\n",
mode ? "true" : "false", rc);
return rc;
}
/**
* cpr3_regulator_config_kryo_ldo_mem_acc() - configure the mem-acc regulator
* corner based upon a future Kryo LDO regulator voltage setpoint
* @vreg: Pointer to the CPR3 regulator
* @new_volt: New voltage in microvolts that the LDO regulator needs
* to end up at
*
* This function determines if a new LDO regulator set point will result
* in crossing the voltage threshold that requires reconfiguration of
* the mem-acc regulator associated with a CPR3 regulator and if so, performs
* the correct sequence to select the correct mem-acc corner.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_kryo_ldo_mem_acc(struct cpr3_regulator *vreg,
int new_volt)
{
struct cpr3_controller *ctrl = vreg->thread->ctrl;
struct regulator *ldo_reg = vreg->ldo_regulator;
struct regulator *mem_acc_reg = vreg->mem_acc_regulator;
int mem_acc_volt = ctrl->mem_acc_threshold_volt;
int last_volt, safe_volt, mem_acc_corn, rc;
enum msm_apm_supply apm_mode;
if (!mem_acc_reg || !mem_acc_volt || !ldo_reg)
return 0;
apm_mode = msm_apm_get_supply(ctrl->apm);
if (apm_mode < 0) {
cpr3_err(ctrl, "APM get supply failed, rc=%d\n",
apm_mode);
return apm_mode;
}
last_volt = regulator_get_voltage(ldo_reg);
if (last_volt < 0) {
cpr3_err(vreg, "regulator_get_voltage(ldo) failed, rc=%d\n",
last_volt);
return last_volt;
}
if (((last_volt < mem_acc_volt && mem_acc_volt <= new_volt)
|| (last_volt >= mem_acc_volt && mem_acc_volt > new_volt))) {
if (apm_mode == ctrl->apm_high_supply)
safe_volt = min(vreg->ldo_max_volt, mem_acc_volt);
else
safe_volt = min(max(ctrl->system_supply_max_volt -
vreg->ldo_max_headroom_volt,
mem_acc_volt), vreg->ldo_max_volt);
rc = regulator_set_voltage(ldo_reg, safe_volt,
max(new_volt, last_volt));
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
mem_acc_volt, rc);
return rc;
}
mem_acc_corn = new_volt < mem_acc_volt ?
ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER] :
ctrl->mem_acc_corner_map[CPR3_MEM_ACC_HIGH_CORNER];
rc = regulator_set_voltage(mem_acc_reg, mem_acc_corn,
mem_acc_corn);
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
0, rc);
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_kryo_bhs_prepare() - configure the Kryo LDO regulator
* associated with a CPR3 regulator in preparation for BHS
* mode switch.
* @vreg: Pointer to the CPR3 regulator
* @vdd_volt: Last known settled voltage in microvolts for the VDD
* supply
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
*
* This function performs the necessary steps prior to switching a Kryo LDO
* regulator to BHS mode (LDO bypassed mode).
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_kryo_bhs_prepare(struct cpr3_regulator *vreg,
int vdd_volt, int vdd_ceiling_volt)
{
struct regulator *ldo_reg = vreg->ldo_regulator;
int bhs_volt, rc;
bhs_volt = vdd_volt - vreg->ldo_min_headroom_volt;
if (bhs_volt > vreg->ldo_max_volt) {
cpr3_debug(vreg, "limited to LDO output of %d uV when switching to BHS mode\n",
vreg->ldo_max_volt);
bhs_volt = vreg->ldo_max_volt;
}
rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg, bhs_volt);
if (rc) {
cpr3_err(vreg, "failed to configure mem-acc settings\n");
return rc;
}
rc = regulator_set_voltage(ldo_reg, bhs_volt, min(vdd_ceiling_volt,
vreg->ldo_max_volt));
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
bhs_volt, rc);
return rc;
}
return rc;
}
/**
* cpr3_regulator_set_bhs_mode() - configure the LDO regulator associated with
* a CPR3 regulator to BHS mode
* @vreg: Pointer to the CPR3 regulator
* @vdd_volt: Last known settled voltage in microvolts for the VDD
* supply
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
*
* This function performs the necessary steps to switch an LDO regulator
* to BHS mode (LDO bypassed mode).
*/
static int cpr3_regulator_set_bhs_mode(struct cpr3_regulator *vreg,
int vdd_volt, int vdd_ceiling_volt)
{
struct regulator *ldo_reg = vreg->ldo_regulator;
int rc;
if (vreg->ldo_type == CPR3_LDO_KRYO) {
rc = cpr3_regulator_kryo_bhs_prepare(vreg, vdd_volt,
vdd_ceiling_volt);
if (rc) {
cpr3_err(vreg, "cpr3 regulator bhs mode prepare failed, rc=%d\n",
rc);
return rc;
}
}
rc = regulator_allow_bypass(ldo_reg, BHS_MODE);
if (rc) {
cpr3_err(vreg, "regulator_allow_bypass(bhs) == %s failed, rc=%d\n",
BHS_MODE ? "true" : "false", rc);
return rc;
}
vreg->ldo_regulator_bypass = BHS_MODE;
return rc;
}
/**
* cpr3_regulator_ldo_apm_prepare() - configure LDO regulators associated
* with each CPR3 regulator of a CPR3 controller in preparation
* for an APM switch.
* @ctrl: Pointer to the CPR3 controller
* @new_volt: New voltage in microvolts that the VDD supply
* needs to end up at
* @last_volt: Last known voltage in microvolts for the VDD supply
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* This function ensures LDO regulator hardware requirements are met before
* an APM switch is requested. The function must be called as the last step
* before switching the APM mode.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_ldo_apm_prepare(struct cpr3_controller *ctrl,
int new_volt, int last_volt,
struct cpr3_corner *aggr_corner)
{
struct cpr3_regulator *vreg;
struct cpr3_corner *current_corner;
enum msm_apm_supply apm_mode;
int i, j, safe_volt, max_volt, ldo_volt, ref_volt, rc;
apm_mode = msm_apm_get_supply(ctrl->apm);
if (apm_mode < 0) {
cpr3_err(ctrl, "APM get supply failed, rc=%d\n", apm_mode);
return apm_mode;
}
if (apm_mode == ctrl->apm_low_supply ||
new_volt >= ctrl->apm_threshold_volt)
return 0;
/*
* Guarantee LDO maximum headroom is not violated when the APM is
* switched to the system-supply source.
*/
for (i = 0; i < ctrl->thread_count; i++) {
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
vreg = &ctrl->thread[i].vreg[j];
if (!vreg->vreg_enabled || vreg->current_corner
== CPR3_REGULATOR_CORNER_INVALID)
continue;
if (!vreg->ldo_regulator || !vreg->ldo_mode_allowed ||
vreg->ldo_regulator_bypass == BHS_MODE)
continue;
/*
* If the new VDD configuration does not satisfy
* requirements for LDO usage, switch the regulator
* to BHS mode. By doing so, the LDO maximum headroom
* does not need to be enforced.
*/
current_corner = &vreg->corner[vreg->current_corner];
ldo_volt = current_corner->open_loop_volt
- vreg->ldo_adjust_volt;
ref_volt = ctrl->use_hw_closed_loop ?
aggr_corner->floor_volt :
new_volt;
if (ref_volt < ldo_volt + vreg->ldo_min_headroom_volt
|| ldo_volt < ctrl->system_supply_max_volt -
vreg->ldo_max_headroom_volt ||
ldo_volt > vreg->ldo_max_volt) {
rc = cpr3_regulator_set_bhs_mode(vreg,
last_volt, aggr_corner->ceiling_volt);
if (rc)
return rc;
/*
* Do not enforce LDO maximum headroom since the
* regulator is now configured to BHS mode.
*/
continue;
}
safe_volt = min(max(ldo_volt,
ctrl->system_supply_max_volt
- vreg->ldo_max_headroom_volt),
vreg->ldo_max_volt);
max_volt = min(ctrl->system_supply_max_volt,
vreg->ldo_max_volt);
rc = regulator_set_voltage(vreg->ldo_regulator,
safe_volt, max_volt);
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
safe_volt, rc);
return rc;
}
}
}
return 0;
}
/**
* cpr3_regulator_config_vreg_kryo_ldo() - configure the voltage and bypass
* state for the Kryo LDO regulator associated with a single CPR3
* regulator.
*
* @vreg: Pointer to the CPR3 regulator
* @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
* the VDD supply
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
* @ref_volt: Reference voltage in microvolts corresponds either to
* the aggregated floor voltage or the next VDD supply
* setpoint.
* @last_volt: Last known voltage in microvolts for the VDD supply
*
* This function performs all relevant LDO or BHS configurations if a Kryo LDO
* regulator is specified.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_vreg_kryo_ldo(struct cpr3_regulator *vreg,
int vdd_floor_volt, int vdd_ceiling_volt,
int ref_volt, int last_volt)
{
struct cpr3_controller *ctrl = vreg->thread->ctrl;
struct regulator *ldo_reg = vreg->ldo_regulator;
struct cpr3_corner *current_corner;
enum msm_apm_supply apm_mode;
int rc, ldo_volt, final_ldo_volt, bhs_volt, max_volt, safe_volt;
current_corner = &vreg->corner[vreg->current_corner];
ldo_volt = current_corner->open_loop_volt
- vreg->ldo_adjust_volt;
bhs_volt = last_volt - vreg->ldo_min_headroom_volt;
max_volt = min(vdd_ceiling_volt, vreg->ldo_max_volt);
if (ref_volt >= ldo_volt + vreg->ldo_min_headroom_volt &&
ldo_volt >= ctrl->system_supply_max_volt -
vreg->ldo_max_headroom_volt &&
bhs_volt >= ctrl->system_supply_max_volt -
vreg->ldo_max_headroom_volt &&
ldo_volt <= vreg->ldo_max_volt) {
/* LDO minimum and maximum headrooms satisfied */
apm_mode = msm_apm_get_supply(ctrl->apm);
if (apm_mode < 0) {
cpr3_err(ctrl, "APM get supply failed, rc=%d\n",
apm_mode);
return apm_mode;
}
if (vreg->ldo_regulator_bypass == BHS_MODE) {
/*
* BHS to LDO transition. Configure LDO output
* to min(max LDO output, VDD - LDO headroom)
* voltage if APM is on high supply source or
* min(max(system-supply ceiling - LDO max headroom,
* VDD - LDO headroom), max LDO output) if
* APM is on low supply source, then switch
* regulator mode.
*/
if (apm_mode == ctrl->apm_high_supply)
safe_volt = min(vreg->ldo_max_volt, bhs_volt);
else
safe_volt =
min(max(ctrl->system_supply_max_volt -
vreg->ldo_max_headroom_volt,
bhs_volt),
vreg->ldo_max_volt);
rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg,
safe_volt);
if (rc) {
cpr3_err(vreg, "failed to configure mem-acc settings\n");
return rc;
}
rc = regulator_set_voltage(ldo_reg, safe_volt,
max_volt);
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
safe_volt, rc);
return rc;
}
rc = regulator_allow_bypass(ldo_reg, LDO_MODE);
if (rc) {
cpr3_err(vreg, "regulator_allow_bypass(ldo) == %s failed, rc=%d\n",
LDO_MODE ? "true" : "false", rc);
return rc;
}
vreg->ldo_regulator_bypass = LDO_MODE;
}
/* Configure final LDO output voltage */
if (apm_mode == ctrl->apm_high_supply)
final_ldo_volt = max(ldo_volt,
vdd_ceiling_volt -
vreg->ldo_max_headroom_volt);
else
final_ldo_volt = ldo_volt;
rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg,
final_ldo_volt);
if (rc) {
cpr3_err(vreg, "failed to configure mem-acc settings\n");
return rc;
}
rc = regulator_set_voltage(ldo_reg, final_ldo_volt, max_volt);
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
final_ldo_volt, rc);
return rc;
}
} else {
if (vreg->ldo_regulator_bypass == LDO_MODE) {
/* LDO to BHS transition */
rc = cpr3_regulator_set_bhs_mode(vreg, last_volt,
vdd_ceiling_volt);
if (rc)
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_config_vreg_ldo300() - configure the voltage and bypass state
* for the LDO300 regulator associated with a single CPR3
* regulator.
*
* @vreg: Pointer to the CPR3 regulator
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
*
* This function performs all relevant LDO or BHS configurations for an LDO300
* type regulator.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_vreg_ldo300(struct cpr3_regulator *vreg,
int new_volt, int vdd_ceiling_volt)
{
struct regulator *ldo_reg = vreg->ldo_regulator;
struct cpr3_corner *corner;
bool mode;
int rc = 0;
corner = &vreg->corner[vreg->current_corner];
mode = corner->ldo_mode_allowed ? LDO_MODE : BHS_MODE;
if (mode == LDO_MODE) {
rc = regulator_set_voltage(ldo_reg, new_volt, vdd_ceiling_volt);
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
new_volt, rc);
return rc;
}
}
if (vreg->ldo_regulator_bypass != mode) {
rc = regulator_allow_bypass(ldo_reg, mode);
if (rc) {
cpr3_err(vreg, "regulator_allow_bypass(%s) is failed, rc=%d\n",
mode == LDO_MODE ? "ldo" : "bhs", rc);
return rc;
}
vreg->ldo_regulator_bypass = mode;
}
return rc;
}
/**
* cpr3_regulator_config_vreg_ldo() - configure the voltage and bypass state for
* the LDO regulator associated with a single CPR3 regulator.
*
* @vreg: Pointer to the CPR3 regulator
* @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
* the VDD supply
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @last_volt: Last known voltage in microvolts for the VDD supply
*
* This function identifies the type of LDO regulator associated with a CPR3
* regulator and invokes the LDO specific configuration functions.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_vreg_ldo(struct cpr3_regulator *vreg,
int vdd_floor_volt, int vdd_ceiling_volt,
int new_volt, int last_volt)
{
struct cpr3_controller *ctrl = vreg->thread->ctrl;
int ref_volt, rc;
ref_volt = ctrl->use_hw_closed_loop ? vdd_floor_volt :
new_volt;
rc = cpr3_regulator_config_ldo_retention(vreg, ref_volt);
if (rc)
return rc;
if (!vreg->vreg_enabled ||
vreg->current_corner == CPR3_REGULATOR_CORNER_INVALID)
return 0;
switch (vreg->ldo_type) {
case CPR3_LDO_KRYO:
rc = cpr3_regulator_config_vreg_kryo_ldo(vreg, vdd_floor_volt,
vdd_ceiling_volt, ref_volt, last_volt);
if (rc)
cpr3_err(vreg, "kryo ldo regulator config failed, rc=%d\n",
rc);
break;
case CPR3_LDO300:
rc = cpr3_regulator_config_vreg_ldo300(vreg, new_volt,
vdd_ceiling_volt);
if (rc)
cpr3_err(vreg, "ldo300 regulator config failed, rc=%d\n",
rc);
break;
default:
cpr3_err(vreg, "invalid ldo regulator type = %d\n",
vreg->ldo_type);
rc = -EINVAL;
}
return rc;
}
/**
* cpr3_regulator_config_ldo() - configure the voltage and bypass state for the
* LDO regulator associated with each CPR3 regulator of a CPR3
* controller
* @ctrl: Pointer to the CPR3 controller
* @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
* the VDD supply
* @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
* the VDD supply
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @last_volt: Last known voltage in microvolts for the VDD supply
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_ldo(struct cpr3_controller *ctrl,
int vdd_floor_volt, int vdd_ceiling_volt,
int new_volt, int last_volt)
{
struct cpr3_regulator *vreg;
int i, j, rc;
for (i = 0; i < ctrl->thread_count; i++) {
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
vreg = &ctrl->thread[i].vreg[j];
if (!vreg->ldo_regulator || !vreg->ldo_mode_allowed)
continue;
rc = cpr3_regulator_config_vreg_ldo(vreg,
vdd_floor_volt, vdd_ceiling_volt,
new_volt, last_volt);
if (rc)
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_mem_acc_bhs_used() - determines if mem-acc regulators powered
* through a BHS are associated with the CPR3 controller or any of
* the CPR3 regulators it controls.
* @ctrl: Pointer to the CPR3 controller
*
* This function determines if the CPR3 controller or any of its CPR3 regulators
* need to manage mem-acc regulators that are currently powered through a BHS
* and whose corner selection is based upon a particular voltage threshold.
*
* Return: true or false
*/
static bool cpr3_regulator_mem_acc_bhs_used(struct cpr3_controller *ctrl)
{
struct cpr3_regulator *vreg;
int i, j;
if (!ctrl->mem_acc_threshold_volt)
return false;
if (ctrl->mem_acc_regulator)
return true;
for (i = 0; i < ctrl->thread_count; i++) {
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
vreg = &ctrl->thread[i].vreg[j];
if (vreg->mem_acc_regulator &&
(!vreg->ldo_regulator ||
vreg->ldo_regulator_bypass
== BHS_MODE))
return true;
}
}
return false;
}
/**
* cpr3_regulator_config_bhs_mem_acc() - configure the mem-acc regulator
* settings for hardware blocks currently powered through the BHS.
* @ctrl: Pointer to the CPR3 controller
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @last_volt: Pointer to the last known voltage in microvolts for the
* VDD supply
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* This function programs the mem-acc regulator corners for CPR3 regulators
* whose LDO regulators are in bypassed state. The function also handles
* CPR3 controllers which utilize mem-acc regulators that operate independently
* from the LDO hardware and that must be programmed when the VDD supply
* crosses a particular voltage threshold.
*
* Return: 0 on success, errno on failure. If the VDD supply voltage is
* modified, last_volt is updated to reflect the new voltage setpoint.
*/
static int cpr3_regulator_config_bhs_mem_acc(struct cpr3_controller *ctrl,
int new_volt, int *last_volt,
struct cpr3_corner *aggr_corner)
{
struct cpr3_regulator *vreg;
int i, j, rc, mem_acc_corn, safe_volt;
int mem_acc_volt = ctrl->mem_acc_threshold_volt;
int ref_volt;
if (!cpr3_regulator_mem_acc_bhs_used(ctrl))
return 0;
ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
new_volt;
if (((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
(*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))) {
if (ref_volt < *last_volt)
safe_volt = max(mem_acc_volt, aggr_corner->last_volt);
else
safe_volt = max(mem_acc_volt, *last_volt);
rc = regulator_set_voltage(ctrl->vdd_regulator, safe_volt,
new_volt < *last_volt ?
ctrl->aggr_corner.ceiling_volt :
new_volt);
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
safe_volt, rc);
return rc;
}
*last_volt = safe_volt;
mem_acc_corn = ref_volt < mem_acc_volt ?
ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER] :
ctrl->mem_acc_corner_map[CPR3_MEM_ACC_HIGH_CORNER];
if (ctrl->mem_acc_regulator) {
rc = regulator_set_voltage(ctrl->mem_acc_regulator,
mem_acc_corn, mem_acc_corn);
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
mem_acc_corn, rc);
return rc;
}
}
for (i = 0; i < ctrl->thread_count; i++) {
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
vreg = &ctrl->thread[i].vreg[j];
if (!vreg->mem_acc_regulator ||
(vreg->ldo_regulator &&
vreg->ldo_regulator_bypass
== LDO_MODE))
continue;
rc = regulator_set_voltage(
vreg->mem_acc_regulator, mem_acc_corn,
mem_acc_corn);
if (rc) {
cpr3_err(vreg, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
mem_acc_corn, rc);
return rc;
}
}
}
}
return 0;
}
/**
* cpr3_regulator_switch_apm_mode() - switch the mode of the APM controller
* associated with a given CPR3 controller
* @ctrl: Pointer to the CPR3 controller
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @last_volt: Pointer to the last known voltage in microvolts for the
* VDD supply
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* This function requests a switch of the APM mode while guaranteeing
* any LDO regulator hardware requirements are satisfied. The function must
* be called once it is known a new VDD supply setpoint crosses the APM
* voltage threshold.
*
* Return: 0 on success, errno on failure. If the VDD supply voltage is
* modified, last_volt is updated to reflect the new voltage setpoint.
*/
static int cpr3_regulator_switch_apm_mode(struct cpr3_controller *ctrl,
int new_volt, int *last_volt,
struct cpr3_corner *aggr_corner)
{
struct regulator *vdd = ctrl->vdd_regulator;
int apm_volt = ctrl->apm_threshold_volt;
int orig_last_volt = *last_volt;
int rc;
rc = regulator_set_voltage(vdd, apm_volt, apm_volt);
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
apm_volt, rc);
return rc;
}
*last_volt = apm_volt;
rc = cpr3_regulator_ldo_apm_prepare(ctrl, new_volt, *last_volt,
aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to prepare LDO state for APM switch, rc=%d\n",
rc);
return rc;
}
rc = msm_apm_set_supply(ctrl->apm, new_volt >= apm_volt
? ctrl->apm_high_supply : ctrl->apm_low_supply);
if (rc) {
cpr3_err(ctrl, "APM switch failed, rc=%d\n", rc);
/* Roll back the voltage. */
regulator_set_voltage(vdd, orig_last_volt, INT_MAX);
*last_volt = orig_last_volt;
return rc;
}
return 0;
}
/**
* cpr3_regulator_config_voltage_crossings() - configure APM and mem-acc
* settings depending upon a new VDD supply setpoint
*
* @ctrl: Pointer to the CPR3 controller
* @new_volt: New voltage in microvolts that VDD supply needs to
* end up at
* @last_volt: Pointer to the last known voltage in microvolts for the
* VDD supply
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* This function handles the APM and mem-acc regulator reconfiguration if
* the new VDD supply voltage will result in crossing their respective voltage
* thresholds.
*
* Return: 0 on success, errno on failure. If the VDD supply voltage is
* modified, last_volt is updated to reflect the new voltage setpoint.
*/
static int cpr3_regulator_config_voltage_crossings(struct cpr3_controller *ctrl,
int new_volt, int *last_volt,
struct cpr3_corner *aggr_corner)
{
bool apm_crossing = false, mem_acc_crossing = false;
bool mem_acc_bhs_used;
int apm_volt = ctrl->apm_threshold_volt;
int mem_acc_volt = ctrl->mem_acc_threshold_volt;
int ref_volt, rc;
if (ctrl->apm && apm_volt > 0
&& ((*last_volt < apm_volt && apm_volt <= new_volt)
|| (*last_volt >= apm_volt && apm_volt > new_volt)))
apm_crossing = true;
mem_acc_bhs_used = cpr3_regulator_mem_acc_bhs_used(ctrl);
ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
new_volt;
if (mem_acc_bhs_used &&
(((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
(*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))))
mem_acc_crossing = true;
if (apm_crossing && mem_acc_crossing) {
if ((new_volt < *last_volt && apm_volt >= mem_acc_volt) ||
(new_volt >= *last_volt && apm_volt < mem_acc_volt)) {
rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
last_volt,
aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to switch APM mode\n");
return rc;
}
rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
last_volt, aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
return rc;
}
} else {
rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
last_volt, aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
return rc;
}
rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
last_volt,
aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to switch APM mode\n");
return rc;
}
}
} else if (apm_crossing) {
rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt, last_volt,
aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to switch APM mode\n");
return rc;
}
} else if (mem_acc_crossing) {
rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
last_volt, aggr_corner);
if (rc) {
cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_config_mem_acc() - configure the corner of the mem-acc
* regulator associated with the CPR3 controller
* @ctrl: Pointer to the CPR3 controller
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_config_mem_acc(struct cpr3_controller *ctrl,
struct cpr3_corner *aggr_corner)
{
int rc;
if (ctrl->mem_acc_regulator && aggr_corner->mem_acc_volt) {
rc = regulator_set_voltage(ctrl->mem_acc_regulator,
aggr_corner->mem_acc_volt,
aggr_corner->mem_acc_volt);
if (rc) {
cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
aggr_corner->mem_acc_volt, rc);
return rc;
}
}
return 0;
}
/**
* cpr3_regulator_scale_vdd_voltage() - scale the CPR controlled VDD supply
* voltage to the new level while satisfying any other hardware
* requirements
* @ctrl: Pointer to the CPR3 controller
* @new_volt: New voltage in microvolts that VDD supply needs to end
* up at
* @last_volt: Last known voltage in microvolts for the VDD supply
* @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
* corner aggregated from all CPR3 threads managed by the
* CPR3 controller
*
* This function scales the CPR controlled VDD supply voltage from its
* current level to the new voltage that is specified. If the supply is
* configured to use the APM and the APM threshold is crossed as a result of
* the voltage scaling, then this function also stops at the APM threshold,
* switches the APM source, and finally sets the final new voltage.
*
* Return: 0 on success, errno on failure
*/
static int cpr3_regulator_scale_vdd_voltage(struct cpr3_controller *ctrl,
int new_volt, int last_volt,
struct cpr3_corner *aggr_corner)
{
struct regulator *vdd = ctrl->vdd_regulator;
int rc;
if (new_volt < last_volt) {
if (ctrl->support_ldo300_vreg) {
rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
if (rc)
return rc;
}
/* Decreasing VDD voltage */
rc = cpr3_regulator_config_ldo(ctrl, aggr_corner->floor_volt,
ctrl->aggr_corner.ceiling_volt,
new_volt, last_volt);