4642 lines
135 KiB
C
4642 lines
135 KiB
C
/*
|
|
* Copyright (c) 2015, 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/kryo-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_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_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_ADJ_CTL 0x7F8
|
|
#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_PMIC_STEP_SIZE_MASK GENMASK(16, 12)
|
|
#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT 12
|
|
|
|
/*
|
|
* 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
|
|
|
|
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)
|
|
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_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;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
|
|
{
|
|
u32 pmic_step_size = 1;
|
|
|
|
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));
|
|
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;
|
|
|
|
/* 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) {
|
|
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) {
|
|
rc = regulator_enable(ctrl->vdd_limit_regulator);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Ensure that all register writes complete before disabling clocks. */
|
|
wmb();
|
|
|
|
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)
|
|
mode = ctrl->use_hw_closed_loop
|
|
? "HW closed-loop" : "SW closed-loop";
|
|
else
|
|
mode = "closed-loop";
|
|
|
|
cpr3_info(ctrl, "Default CPR mode = %s", mode);
|
|
|
|
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;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
else
|
|
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 kryo_supply_mode mode;
|
|
|
|
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_ldo_mem_acc() - configure the mem-acc regulator
|
|
* corner based upon a future 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_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_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 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_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;
|
|
}
|
|
|
|
rc = regulator_allow_bypass(ldo_reg, BHS_MODE);
|
|
if (rc) {
|
|
cpr3_err(vreg, "regulator_allow_bypass(ldo) == %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_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 performs all relevant LDO or BHS configurations if an LDO
|
|
* regulator is specified.
|
|
*
|
|
* 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;
|
|
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;
|
|
int ref_volt;
|
|
|
|
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;
|
|
|
|
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_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_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_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) {
|
|
/* Decreasing VDD voltage */
|
|
rc = cpr3_regulator_config_ldo(ctrl, aggr_corner->floor_volt,
|
|
ctrl->aggr_corner.ceiling_volt,
|
|
new_volt, last_volt);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "unable to configure LDO state, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
|
|
if (rc)
|
|
return rc;
|
|
|
|
} else {
|
|
/* Increasing VDD voltage */
|
|
if (ctrl->system_regulator) {
|
|
rc = regulator_set_voltage(ctrl->system_regulator,
|
|
aggr_corner->system_volt, INT_MAX);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
|
|
aggr_corner->system_volt, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = cpr3_regulator_config_voltage_crossings(ctrl, new_volt, &last_volt,
|
|
aggr_corner);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "unable to handle voltage threshold crossing configurations, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Subtract a small amount from the min_uV parameter so that the
|
|
* set voltage request is not dropped by the framework due to being
|
|
* duplicate. This is needed in order to switch from hardware
|
|
* closed-loop to open-loop successfully.
|
|
*/
|
|
rc = regulator_set_voltage(vdd, new_volt - (ctrl->cpr_enabled ? 0 : 1),
|
|
aggr_corner->ceiling_volt);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
|
|
new_volt, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (new_volt >= last_volt) {
|
|
/* Increasing VDD voltage */
|
|
rc = cpr3_regulator_config_ldo(ctrl, aggr_corner->floor_volt,
|
|
aggr_corner->ceiling_volt,
|
|
new_volt, new_volt);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "unable to configure LDO state, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
|
|
if (rc)
|
|
return rc;
|
|
} else {
|
|
/* Decreasing VDD voltage */
|
|
if (ctrl->system_regulator) {
|
|
rc = regulator_set_voltage(ctrl->system_regulator,
|
|
aggr_corner->system_volt, INT_MAX);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
|
|
aggr_corner->system_volt, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_get_dynamic_floor_volt() - returns the current dynamic floor
|
|
* voltage based upon static configurations and the state of all
|
|
* power domains during the last CPR measurement
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
* @reg_last_measurement: Value read from the LAST_MEASUREMENT register
|
|
*
|
|
* When using HW closed-loop, the dynamic floor voltage is always returned
|
|
* regardless of the current state of the power domains.
|
|
*
|
|
* Return: dynamic floor voltage in microvolts or 0 if dynamic floor is not
|
|
* currently required
|
|
*/
|
|
static int cpr3_regulator_get_dynamic_floor_volt(struct cpr3_controller *ctrl,
|
|
u32 reg_last_measurement)
|
|
{
|
|
int dynamic_floor_volt = 0;
|
|
struct cpr3_regulator *vreg;
|
|
bool valid, pd_valid;
|
|
u32 bypass_bits;
|
|
int i, j;
|
|
|
|
if (!ctrl->supports_hw_closed_loop)
|
|
return 0;
|
|
|
|
if (likely(!ctrl->use_hw_closed_loop)) {
|
|
valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
|
|
bypass_bits
|
|
= (reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
|
|
>> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT;
|
|
} else {
|
|
/*
|
|
* Ensure that the dynamic floor voltage is always used for
|
|
* HW closed-loop since the conditions below cannot be evaluated
|
|
* after each CPR measurement.
|
|
*/
|
|
valid = false;
|
|
bypass_bits = 0;
|
|
}
|
|
|
|
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->uses_dynamic_floor)
|
|
continue;
|
|
|
|
pd_valid = !((bypass_bits & vreg->pd_bypass_mask)
|
|
== vreg->pd_bypass_mask);
|
|
|
|
if (!valid || !pd_valid)
|
|
dynamic_floor_volt = max(dynamic_floor_volt,
|
|
vreg->corner[
|
|
vreg->dynamic_floor_corner].last_volt);
|
|
}
|
|
}
|
|
|
|
return dynamic_floor_volt;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_aggregate_corners() - aggregate two corners together
|
|
* @aggr_corner: Pointer to accumulated aggregated corner which
|
|
* is both an input and an output
|
|
* @corner: Pointer to the corner to be aggregated with
|
|
* aggr_corner
|
|
* @aggr_quot: Flag indicating that target quotients should be
|
|
* aggregated as well.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_aggregate_corners(struct cpr3_corner *aggr_corner,
|
|
const struct cpr3_corner *corner, bool aggr_quot)
|
|
{
|
|
int i;
|
|
|
|
aggr_corner->ceiling_volt
|
|
= max(aggr_corner->ceiling_volt, corner->ceiling_volt);
|
|
aggr_corner->floor_volt
|
|
= max(aggr_corner->floor_volt, corner->floor_volt);
|
|
aggr_corner->last_volt
|
|
= max(aggr_corner->last_volt, corner->last_volt);
|
|
aggr_corner->open_loop_volt
|
|
= max(aggr_corner->open_loop_volt, corner->open_loop_volt);
|
|
aggr_corner->system_volt
|
|
= max(aggr_corner->system_volt, corner->system_volt);
|
|
aggr_corner->mem_acc_volt
|
|
= max(aggr_corner->mem_acc_volt, corner->mem_acc_volt);
|
|
aggr_corner->irq_en |= corner->irq_en;
|
|
|
|
if (aggr_quot) {
|
|
aggr_corner->ro_mask &= corner->ro_mask;
|
|
|
|
for (i = 0; i < CPR3_RO_COUNT; i++)
|
|
aggr_corner->target_quot[i]
|
|
= max(aggr_corner->target_quot[i],
|
|
corner->target_quot[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
|
|
* to reflect the corners used by all CPR3 regulators as well as
|
|
* the CPR operating mode
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* This function aggregates the CPR parameters for all CPR3 regulators
|
|
* associated with the VDD supply. Upon success, it sets the aggregated last
|
|
* known good voltage.
|
|
*
|
|
* The VDD supply voltage will not be physically configured unless this
|
|
* condition is met by at least one of the regulators of the controller:
|
|
* regulator->vreg_enabled == true &&
|
|
* regulator->current_corner != CPR3_REGULATOR_CORNER_INVALID
|
|
*
|
|
* CPR registers for the controller and each thread are updated as long as
|
|
* ctrl->cpr_enabled == true.
|
|
*
|
|
* Note, CPR3 controller lock must be held by the caller.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int _cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
|
|
{
|
|
struct cpr3_corner aggr_corner = {};
|
|
struct cpr3_thread *thread;
|
|
struct cpr3_regulator *vreg;
|
|
bool valid = false;
|
|
bool thread_valid;
|
|
int i, j, rc, new_volt, vdd_volt, dynamic_floor_volt;
|
|
u32 reg_last_measurement = 0;
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
vdd_volt = regulator_get_voltage(ctrl->vdd_regulator);
|
|
if (vdd_volt < 0) {
|
|
cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
|
|
vdd_volt);
|
|
return vdd_volt;
|
|
}
|
|
|
|
if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop)
|
|
reg_last_measurement
|
|
= cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
|
|
|
|
/* Aggregate the requests of all threads */
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
thread = &ctrl->thread[i];
|
|
thread_valid = false;
|
|
memset(&thread->aggr_corner, 0, sizeof(thread->aggr_corner));
|
|
thread->aggr_corner.ro_mask = CPR3_RO_MASK;
|
|
|
|
for (j = 0; j < thread->vreg_count; j++) {
|
|
vreg = &thread->vreg[j];
|
|
|
|
if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop)
|
|
cpr3_update_vreg_closed_loop_volt(vreg,
|
|
vdd_volt, reg_last_measurement);
|
|
|
|
if (!vreg->vreg_enabled
|
|
|| vreg->current_corner
|
|
== CPR3_REGULATOR_CORNER_INVALID) {
|
|
/* Cannot participate in aggregation. */
|
|
vreg->aggregated = false;
|
|
continue;
|
|
} else {
|
|
vreg->aggregated = true;
|
|
thread_valid = true;
|
|
}
|
|
|
|
cpr3_regulator_aggregate_corners(&thread->aggr_corner,
|
|
&vreg->corner[vreg->current_corner], true);
|
|
}
|
|
|
|
valid |= thread_valid;
|
|
|
|
if (thread_valid)
|
|
cpr3_regulator_aggregate_corners(&aggr_corner,
|
|
&thread->aggr_corner, false);
|
|
}
|
|
|
|
if (valid && ctrl->cpr_allowed_hw && ctrl->cpr_allowed_sw) {
|
|
rc = cpr3_closed_loop_enable(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
rc = cpr3_closed_loop_disable(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* No threads are enabled with a valid corner so exit. */
|
|
if (!valid)
|
|
return 0;
|
|
|
|
/*
|
|
* When using CPR hardware closed-loop, the voltage may vary anywhere
|
|
* between the floor and ceiling voltage without software notification.
|
|
* Therefore, it is required that the floor to ceiling range for the
|
|
* aggregated corner not intersect the APM threshold voltage. Adjust
|
|
* the floor to ceiling range if this requirement is violated.
|
|
*
|
|
* The following algorithm is applied in the case that
|
|
* floor < threshold <= ceiling:
|
|
* if open_loop >= threshold - adj, then floor = threshold
|
|
* else ceiling = threshold - step
|
|
* where adj = an adjustment factor to ensure sufficient voltage margin
|
|
* and step = VDD output step size
|
|
*
|
|
* The open-loop and last known voltages are also bounded by the new
|
|
* floor or ceiling value as needed.
|
|
*/
|
|
if (ctrl->use_hw_closed_loop
|
|
&& aggr_corner.ceiling_volt >= ctrl->apm_threshold_volt
|
|
&& aggr_corner.floor_volt < ctrl->apm_threshold_volt) {
|
|
|
|
if (aggr_corner.open_loop_volt
|
|
>= ctrl->apm_threshold_volt - ctrl->apm_adj_volt)
|
|
aggr_corner.floor_volt = ctrl->apm_threshold_volt;
|
|
else
|
|
aggr_corner.ceiling_volt
|
|
= ctrl->apm_threshold_volt - ctrl->step_volt;
|
|
|
|
aggr_corner.last_volt
|
|
= max(aggr_corner.last_volt, aggr_corner.floor_volt);
|
|
aggr_corner.last_volt
|
|
= min(aggr_corner.last_volt, aggr_corner.ceiling_volt);
|
|
aggr_corner.open_loop_volt
|
|
= max(aggr_corner.open_loop_volt, aggr_corner.floor_volt);
|
|
aggr_corner.open_loop_volt
|
|
= min(aggr_corner.open_loop_volt, aggr_corner.ceiling_volt);
|
|
}
|
|
|
|
if (ctrl->use_hw_closed_loop
|
|
&& aggr_corner.ceiling_volt >= ctrl->mem_acc_threshold_volt
|
|
&& aggr_corner.floor_volt < ctrl->mem_acc_threshold_volt) {
|
|
aggr_corner.floor_volt = ctrl->mem_acc_threshold_volt;
|
|
aggr_corner.last_volt = max(aggr_corner.last_volt,
|
|
aggr_corner.floor_volt);
|
|
aggr_corner.open_loop_volt = max(aggr_corner.open_loop_volt,
|
|
aggr_corner.floor_volt);
|
|
}
|
|
|
|
if (ctrl->use_hw_closed_loop) {
|
|
dynamic_floor_volt
|
|
= cpr3_regulator_get_dynamic_floor_volt(ctrl,
|
|
reg_last_measurement);
|
|
if (aggr_corner.floor_volt < dynamic_floor_volt) {
|
|
aggr_corner.floor_volt = dynamic_floor_volt;
|
|
aggr_corner.last_volt = max(aggr_corner.last_volt,
|
|
aggr_corner.floor_volt);
|
|
aggr_corner.open_loop_volt
|
|
= max(aggr_corner.open_loop_volt,
|
|
aggr_corner.floor_volt);
|
|
aggr_corner.ceiling_volt = max(aggr_corner.ceiling_volt,
|
|
aggr_corner.floor_volt);
|
|
}
|
|
}
|
|
|
|
if (ctrl->cpr_enabled && ctrl->last_corner_was_closed_loop) {
|
|
new_volt = aggr_corner.last_volt;
|
|
} else {
|
|
new_volt = aggr_corner.open_loop_volt;
|
|
aggr_corner.last_volt = aggr_corner.open_loop_volt;
|
|
}
|
|
|
|
cpr3_debug(ctrl, "setting new voltage=%d uV\n", new_volt);
|
|
rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
|
|
vdd_volt, &aggr_corner);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "vdd voltage scaling failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Only update registers if CPR is enabled. */
|
|
if (ctrl->cpr_enabled) {
|
|
if (ctrl->use_hw_closed_loop) {
|
|
/* Hardware closed-loop */
|
|
|
|
/* Set ceiling and floor limits in hardware */
|
|
rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
|
|
aggr_corner.floor_volt,
|
|
aggr_corner.ceiling_volt);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not configure HW closed-loop voltage limits, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
/* Software closed-loop */
|
|
|
|
/*
|
|
* Disable UP or DOWN interrupts when at ceiling or
|
|
* floor respectively.
|
|
*/
|
|
if (new_volt == aggr_corner.floor_volt)
|
|
aggr_corner.irq_en &= ~CPR3_IRQ_DOWN;
|
|
if (new_volt == aggr_corner.ceiling_volt)
|
|
aggr_corner.irq_en &= ~CPR3_IRQ_UP;
|
|
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
|
|
CPR3_IRQ_UP | CPR3_IRQ_DOWN);
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_EN, aggr_corner.irq_en);
|
|
}
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
cpr3_regulator_set_target_quot(&ctrl->thread[i]);
|
|
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
|
|
if (vreg->vreg_enabled)
|
|
vreg->last_closed_loop_corner
|
|
= vreg->current_corner;
|
|
}
|
|
}
|
|
|
|
if (ctrl->proc_clock_throttle) {
|
|
if (aggr_corner.ceiling_volt > aggr_corner.floor_volt
|
|
&& (ctrl->use_hw_closed_loop
|
|
|| new_volt < aggr_corner.ceiling_volt))
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
ctrl->proc_clock_throttle);
|
|
else
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
CPR3_PD_THROTTLE_DISABLE);
|
|
}
|
|
|
|
/*
|
|
* Ensure that all CPR register writes complete before
|
|
* re-enabling CPR loop operation.
|
|
*/
|
|
wmb();
|
|
}
|
|
|
|
/*
|
|
* Only enable the CPR controller if it is possible to set more than
|
|
* one vdd-supply voltage.
|
|
*/
|
|
if (aggr_corner.ceiling_volt > aggr_corner.floor_volt)
|
|
cpr3_ctrl_loop_enable(ctrl);
|
|
|
|
ctrl->aggr_corner = aggr_corner;
|
|
ctrl->last_corner_was_closed_loop = ctrl->cpr_enabled;
|
|
|
|
cpr3_debug(ctrl, "CPR configuration updated\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_wait_for_idle() - wait for the CPR controller to no longer be
|
|
* busy
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
* @max_wait_ns: Max wait time in nanoseconds
|
|
*
|
|
* Return: 0 on success or -ETIMEDOUT if the controller was still busy after
|
|
* the maximum delay time
|
|
*/
|
|
static int cpr3_regulator_wait_for_idle(struct cpr3_controller *ctrl,
|
|
s64 max_wait_ns)
|
|
{
|
|
ktime_t start, end;
|
|
s64 time_ns;
|
|
u32 reg;
|
|
|
|
/*
|
|
* Ensure that all previous CPR register writes have completed before
|
|
* checking the status register.
|
|
*/
|
|
mb();
|
|
|
|
start = ktime_get();
|
|
do {
|
|
end = ktime_get();
|
|
time_ns = ktime_to_ns(ktime_sub(end, start));
|
|
if (time_ns > max_wait_ns) {
|
|
cpr3_err(ctrl, "CPR controller still busy after %lld us\n",
|
|
time_ns / 1000);
|
|
return -ETIMEDOUT;
|
|
}
|
|
usleep_range(50, 100);
|
|
reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
|
|
} while (reg & CPR3_CPR_STATUS_BUSY_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cmp_int() - int comparison function to be passed into the sort() function
|
|
* which leads to ascending sorting
|
|
* @a: First int value
|
|
* @b: Second int value
|
|
*
|
|
* Return: >0 if a > b, 0 if a == b, <0 if a < b
|
|
*/
|
|
static int cmp_int(const void *a, const void *b)
|
|
{
|
|
return *(int *)a - *(int *)b;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_measure_aging() - measure the quotient difference for the
|
|
* specified CPR aging sensor
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
* @aging_sensor: Aging sensor to measure
|
|
*
|
|
* Note that vdd-supply must be configured to the aging reference voltage before
|
|
* calling this function.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_measure_aging(struct cpr3_controller *ctrl,
|
|
struct cpr3_aging_sensor_info *aging_sensor)
|
|
{
|
|
u32 mask, reg, result, quot_min, quot_max, sel_min, sel_max;
|
|
u32 quot_min_scaled, quot_max_scaled;
|
|
u32 gcnt, gcnt_ref, gcnt0_restore, gcnt1_restore, irq_restore;
|
|
u32 cont_dly_restore, up_down_dly_restore;
|
|
int quot_delta, quot_delta_scaled, quot_delta_scaled_sum;
|
|
int *quot_delta_results;
|
|
int rc, i, aging_measurement_count, filtered_count;
|
|
bool is_aging_measurement;
|
|
|
|
quot_delta_results = kcalloc(CPR3_AGING_MEASUREMENT_ITERATIONS,
|
|
sizeof(*quot_delta_results), GFP_KERNEL);
|
|
if (!quot_delta_results)
|
|
return -ENOMEM;
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
/* Enable up, down, and mid CPR interrupts */
|
|
irq_restore = cpr3_read(ctrl, CPR3_REG_IRQ_EN);
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_EN,
|
|
CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
|
|
|
|
/* Ensure that the aging sensor is assigned to CPR thread 0 */
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id), 0);
|
|
|
|
/* Switch from HW to SW closed-loop if necessary */
|
|
if (ctrl->supports_hw_closed_loop) {
|
|
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
|
|
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
|
|
CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
|
|
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,
|
|
CPR3_HW_CLOSED_LOOP_DISABLE);
|
|
}
|
|
}
|
|
|
|
/* Configure the GCNT for RO0 and RO1 that are used for aging */
|
|
gcnt0_restore = cpr3_read(ctrl, CPR3_REG_GCNT(0));
|
|
gcnt1_restore = cpr3_read(ctrl, CPR3_REG_GCNT(1));
|
|
gcnt_ref = cpr3_regulator_get_gcnt(ctrl);
|
|
gcnt = gcnt_ref * 3 / 2;
|
|
cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt);
|
|
cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt);
|
|
|
|
/*
|
|
* Mask all sensors except for the one to measure and bypass all
|
|
* sensors in collapsible domains.
|
|
*/
|
|
for (i = 0; i <= ctrl->sensor_count / 32; i++) {
|
|
mask = GENMASK(min(31, ctrl->sensor_count - i * 32), 0);
|
|
if (aging_sensor->sensor_id / 32 >= i
|
|
&& aging_sensor->sensor_id / 32 < (i + 1))
|
|
mask &= ~BIT(aging_sensor->sensor_id % 32);
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), mask);
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i),
|
|
aging_sensor->bypass_mask[i]);
|
|
}
|
|
|
|
/* Set CPR loop delays to 0 us */
|
|
if (ctrl->supports_hw_closed_loop
|
|
&& ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
|
|
cont_dly_restore = cpr3_read(ctrl, CPR3_REG_CPR_TIMER_MID_CONT);
|
|
up_down_dly_restore = cpr3_read(ctrl,
|
|
CPR3_REG_CPR_TIMER_UP_DN_CONT);
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, 0);
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT, 0);
|
|
} else {
|
|
cont_dly_restore = cpr3_read(ctrl,
|
|
CPR3_REG_CPR_TIMER_AUTO_CONT);
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, 0);
|
|
}
|
|
|
|
/* Set count mode to all-at-once min with no repeat */
|
|
cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
|
|
CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
|
|
CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN
|
|
<< CPR3_CPR_CTL_COUNT_MODE_SHIFT);
|
|
|
|
cpr3_ctrl_loop_enable(ctrl);
|
|
|
|
rc = cpr3_regulator_wait_for_idle(ctrl,
|
|
CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
|
|
if (rc)
|
|
goto cleanup;
|
|
|
|
/* Set count mode to all-at-once aging */
|
|
cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL, CPR3_CPR_CTL_COUNT_MODE_MASK,
|
|
CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE
|
|
<< CPR3_CPR_CTL_COUNT_MODE_SHIFT);
|
|
|
|
aging_measurement_count = 0;
|
|
for (i = 0; i < CPR3_AGING_MEASUREMENT_ITERATIONS; i++) {
|
|
/* Send CONT_NACK */
|
|
cpr3_write(ctrl, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_NACK);
|
|
|
|
rc = cpr3_regulator_wait_for_idle(ctrl,
|
|
CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
|
|
if (rc)
|
|
goto cleanup;
|
|
|
|
/* Check for PAGE_IS_AGE flag in status register */
|
|
reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
|
|
is_aging_measurement
|
|
= reg & CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK;
|
|
|
|
/* Read CPR measurement results */
|
|
result = cpr3_read(ctrl, CPR3_REG_RESULT1(0));
|
|
quot_min = (result & CPR3_RESULT1_QUOT_MIN_MASK)
|
|
>> CPR3_RESULT1_QUOT_MIN_SHIFT;
|
|
quot_max = (result & CPR3_RESULT1_QUOT_MAX_MASK)
|
|
>> CPR3_RESULT1_QUOT_MAX_SHIFT;
|
|
sel_min = (result & CPR3_RESULT1_RO_MIN_MASK)
|
|
>> CPR3_RESULT1_RO_MIN_SHIFT;
|
|
sel_max = (result & CPR3_RESULT1_RO_MAX_MASK)
|
|
>> CPR3_RESULT1_RO_MAX_SHIFT;
|
|
|
|
/*
|
|
* Scale the quotients so that they are equivalent to the fused
|
|
* values. This accounts for the difference in measurement
|
|
* interval times.
|
|
*/
|
|
quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1);
|
|
quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1);
|
|
|
|
if (sel_max == 1) {
|
|
quot_delta = quot_max - quot_min;
|
|
quot_delta_scaled = quot_max_scaled - quot_min_scaled;
|
|
} else {
|
|
quot_delta = quot_min - quot_max;
|
|
quot_delta_scaled = quot_min_scaled - quot_max_scaled;
|
|
}
|
|
|
|
if (is_aging_measurement)
|
|
quot_delta_results[aging_measurement_count++]
|
|
= quot_delta_scaled;
|
|
|
|
cpr3_debug(ctrl, "aging results: page_is_age=%u, sel_min=%u, sel_max=%u, quot_min=%u, quot_max=%u, quot_delta=%d, quot_min_scaled=%u, quot_max_scaled=%u, quot_delta_scaled=%d\n",
|
|
is_aging_measurement, sel_min, sel_max, quot_min,
|
|
quot_max, quot_delta, quot_min_scaled, quot_max_scaled,
|
|
quot_delta_scaled);
|
|
}
|
|
|
|
filtered_count
|
|
= aging_measurement_count - CPR3_AGING_MEASUREMENT_FILTER * 2;
|
|
if (filtered_count > 0) {
|
|
sort(quot_delta_results, aging_measurement_count,
|
|
sizeof(*quot_delta_results), cmp_int, NULL);
|
|
|
|
quot_delta_scaled_sum = 0;
|
|
for (i = 0; i < filtered_count; i++)
|
|
quot_delta_scaled_sum
|
|
+= quot_delta_results[i
|
|
+ CPR3_AGING_MEASUREMENT_FILTER];
|
|
|
|
aging_sensor->measured_quot_diff
|
|
= quot_delta_scaled_sum / filtered_count;
|
|
cpr3_info(ctrl, "average quotient delta=%d (count=%d)\n",
|
|
aging_sensor->measured_quot_diff,
|
|
filtered_count);
|
|
} else {
|
|
cpr3_err(ctrl, "%d aging measurements completed after %d iterations\n",
|
|
aging_measurement_count,
|
|
CPR3_AGING_MEASUREMENT_ITERATIONS);
|
|
rc = -EBUSY;
|
|
}
|
|
|
|
cleanup:
|
|
kfree(quot_delta_results);
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_restore);
|
|
|
|
cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt0_restore);
|
|
cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt1_restore);
|
|
|
|
if (ctrl->supports_hw_closed_loop
|
|
&& ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly_restore);
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
|
|
up_down_dly_restore);
|
|
} else {
|
|
cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT,
|
|
cont_dly_restore);
|
|
}
|
|
|
|
for (i = 0; i <= ctrl->sensor_count / 32; i++) {
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), 0);
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i), 0);
|
|
}
|
|
|
|
cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
|
|
CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
|
|
(ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
|
|
| (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT));
|
|
|
|
cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id),
|
|
ctrl->sensor_owner[aging_sensor->sensor_id]);
|
|
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
|
|
CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
|
|
|
|
if (ctrl->supports_hw_closed_loop) {
|
|
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_readjust_quotients() - readjust the target quotients for the
|
|
* regulator by removing the old adjustment and adding the new one
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
* @old_adjust_volt: Old aging adjustment voltage in microvolts
|
|
* @new_adjust_volt: New aging adjustment voltage in microvolts
|
|
*
|
|
* Also reset the cached closed loop voltage (last_volt) to equal the open-loop
|
|
* voltage for each corner.
|
|
*/
|
|
static void cpr3_regulator_readjust_quotients(struct cpr3_regulator *vreg,
|
|
int old_adjust_volt, int new_adjust_volt)
|
|
{
|
|
unsigned long long temp;
|
|
int i, j, old_volt, new_volt;
|
|
|
|
if (!vreg->aging_allowed)
|
|
return;
|
|
|
|
for (i = 0; i < vreg->corner_count; i++) {
|
|
temp = (unsigned long long)old_adjust_volt
|
|
* (unsigned long long)vreg->corner[i].aging_derate;
|
|
do_div(temp, 1000);
|
|
old_volt = temp;
|
|
|
|
temp = (unsigned long long)new_adjust_volt
|
|
* (unsigned long long)vreg->corner[i].aging_derate;
|
|
do_div(temp, 1000);
|
|
new_volt = temp;
|
|
|
|
old_volt = min(vreg->aging_max_adjust_volt, old_volt);
|
|
new_volt = min(vreg->aging_max_adjust_volt, new_volt);
|
|
|
|
for (j = 0; j < CPR3_RO_COUNT; j++) {
|
|
if (vreg->corner[i].target_quot[j] != 0) {
|
|
vreg->corner[i].target_quot[j]
|
|
+= cpr3_quot_adjustment(
|
|
vreg->corner[i].ro_scale[j],
|
|
new_volt)
|
|
- cpr3_quot_adjustment(
|
|
vreg->corner[i].ro_scale[j],
|
|
old_volt);
|
|
}
|
|
}
|
|
vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
|
|
|
|
cpr3_debug(vreg, "corner %d: applying %d uV closed-loop voltage margin adjustment\n",
|
|
i, new_volt);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* cpr3_regulator_set_aging_ref_adjustment() - adjust target quotients for the
|
|
* regulators managed by this CPR controller to account for aging
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
* @ref_adjust_volt: New aging reference adjustment voltage in microvolts to
|
|
* apply to all regulators managed by this CPR controller
|
|
*
|
|
* The existing aging adjustment as defined by ctrl->aging_ref_adjust_volt is
|
|
* first removed and then the adjustment is applied. Lastly, the value of
|
|
* ctrl->aging_ref_adjust_volt is updated to ref_adjust_volt.
|
|
*/
|
|
static void cpr3_regulator_set_aging_ref_adjustment(
|
|
struct cpr3_controller *ctrl, int ref_adjust_volt)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
cpr3_regulator_readjust_quotients(
|
|
&ctrl->thread[i].vreg[j],
|
|
ctrl->aging_ref_adjust_volt,
|
|
ref_adjust_volt);
|
|
}
|
|
}
|
|
|
|
ctrl->aging_ref_adjust_volt = ref_adjust_volt;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_aging_adjust() - adjust the target quotients for regulators
|
|
* based on the output of CPR aging sensors
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_aging_adjust(struct cpr3_controller *ctrl)
|
|
{
|
|
struct cpr3_regulator *vreg;
|
|
struct cpr3_corner restore_aging_corner;
|
|
struct cpr3_corner *corner;
|
|
int *restore_current_corner;
|
|
bool *restore_vreg_enabled;
|
|
int i, j, id, rc, rc2, vreg_count, aging_volt, max_aging_volt;
|
|
u32 reg;
|
|
|
|
if (!ctrl->aging_required || !ctrl->cpr_enabled
|
|
|| ctrl->aggr_corner.ceiling_volt == 0
|
|
|| ctrl->aggr_corner.ceiling_volt > ctrl->aging_ref_volt)
|
|
return 0;
|
|
|
|
for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
vreg_count++;
|
|
|
|
if (vreg->aging_allowed && vreg->vreg_enabled
|
|
&& vreg->current_corner > vreg->aging_corner)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Verify that none of the aging sensors are currently masked. */
|
|
for (i = 0; i < ctrl->aging_sensor_count; i++) {
|
|
id = ctrl->aging_sensor[i].sensor_id;
|
|
reg = cpr3_read(ctrl, CPR3_REG_SENSOR_MASK_READ(id));
|
|
if (reg & BIT(id % 32))
|
|
return 0;
|
|
}
|
|
|
|
restore_current_corner = kcalloc(vreg_count,
|
|
sizeof(*restore_current_corner), GFP_KERNEL);
|
|
restore_vreg_enabled = kcalloc(vreg_count,
|
|
sizeof(*restore_vreg_enabled), GFP_KERNEL);
|
|
if (!restore_current_corner || !restore_vreg_enabled) {
|
|
kfree(restore_current_corner);
|
|
kfree(restore_vreg_enabled);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Force all regulators to the aging corner */
|
|
for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
|
|
restore_current_corner[vreg_count]
|
|
= vreg->current_corner;
|
|
restore_vreg_enabled[vreg_count]
|
|
= vreg->vreg_enabled;
|
|
|
|
vreg->current_corner = vreg->aging_corner;
|
|
vreg->vreg_enabled = true;
|
|
}
|
|
}
|
|
|
|
/* Force one of the regulators to require the aging reference voltage */
|
|
vreg = &ctrl->thread[0].vreg[0];
|
|
corner = &vreg->corner[vreg->current_corner];
|
|
restore_aging_corner = *corner;
|
|
corner->ceiling_volt = ctrl->aging_ref_volt;
|
|
corner->floor_volt = ctrl->aging_ref_volt;
|
|
corner->open_loop_volt = ctrl->aging_ref_volt;
|
|
corner->last_volt = ctrl->aging_ref_volt;
|
|
|
|
/* Skip last_volt caching */
|
|
ctrl->last_corner_was_closed_loop = false;
|
|
|
|
/* Set the vdd supply voltage to the aging reference voltage */
|
|
rc = _cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "unable to force vdd-supply to the aging reference voltage=%d uV, rc=%d\n",
|
|
ctrl->aging_ref_volt, rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ctrl->aging_vdd_mode) {
|
|
rc = regulator_set_mode(ctrl->vdd_regulator,
|
|
ctrl->aging_vdd_mode);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
|
|
ctrl->aging_vdd_mode, rc);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
/* Perform aging measurement on all aging sensors */
|
|
max_aging_volt = 0;
|
|
for (i = 0; i < ctrl->aging_sensor_count; i++) {
|
|
for (j = 0; j < CPR3_AGING_RETRY_COUNT; j++) {
|
|
rc = cpr3_regulator_measure_aging(ctrl,
|
|
&ctrl->aging_sensor[i]);
|
|
if (!rc)
|
|
break;
|
|
}
|
|
|
|
if (!rc) {
|
|
aging_volt =
|
|
cpr3_voltage_adjustment(
|
|
ctrl->aging_sensor[i].ro_scale,
|
|
ctrl->aging_sensor[i].measured_quot_diff
|
|
- ctrl->aging_sensor[i].init_quot_diff);
|
|
max_aging_volt = max(max_aging_volt, aging_volt);
|
|
} else {
|
|
cpr3_err(ctrl, "CPR aging measurement failed after %d tries, rc=%d\n",
|
|
rc, CPR3_AGING_RETRY_COUNT);
|
|
ctrl->aging_failed = true;
|
|
ctrl->aging_required = false;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
vreg = &ctrl->thread[0].vreg[0];
|
|
vreg->corner[vreg->current_corner] = restore_aging_corner;
|
|
|
|
for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
vreg->current_corner
|
|
= restore_current_corner[vreg_count];
|
|
vreg->vreg_enabled = restore_vreg_enabled[vreg_count];
|
|
}
|
|
}
|
|
|
|
kfree(restore_current_corner);
|
|
kfree(restore_vreg_enabled);
|
|
|
|
/* Adjust the CPR target quotients according to the aging measurement */
|
|
if (!rc) {
|
|
cpr3_regulator_set_aging_ref_adjustment(ctrl, max_aging_volt);
|
|
|
|
cpr3_info(ctrl, "aging measurement successful; aging reference adjustment voltage=%d uV\n",
|
|
ctrl->aging_ref_adjust_volt);
|
|
ctrl->aging_succeeded = true;
|
|
ctrl->aging_required = false;
|
|
}
|
|
|
|
if (ctrl->aging_complete_vdd_mode) {
|
|
rc = regulator_set_mode(ctrl->vdd_regulator,
|
|
ctrl->aging_complete_vdd_mode);
|
|
if (rc)
|
|
cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
|
|
ctrl->aging_complete_vdd_mode, rc);
|
|
}
|
|
|
|
/* Skip last_volt caching */
|
|
ctrl->last_corner_was_closed_loop = false;
|
|
|
|
/*
|
|
* Restore vdd-supply to the voltage before the aging measurement and
|
|
* restore the CPR3 controller hardware state.
|
|
*/
|
|
rc2 = _cpr3_regulator_update_ctrl_state(ctrl);
|
|
|
|
/* Stop last_volt caching on for the next request */
|
|
ctrl->last_corner_was_closed_loop = false;
|
|
|
|
return rc ? rc : rc2;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
|
|
* to reflect the corners used by all CPR3 regulators as well as
|
|
* the CPR operating mode and perform aging adjustments if needed
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Note, CPR3 controller lock must be held by the caller.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
|
|
{
|
|
int rc;
|
|
|
|
rc = _cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return cpr3_regulator_aging_adjust(ctrl);
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_set_voltage() - set the voltage corner for the CPR3 regulator
|
|
* associated with the regulator device
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
* @corner: New voltage corner to set (offset by CPR3_CORNER_OFFSET)
|
|
* @corner_max: Maximum voltage corner allowed (offset by
|
|
* CPR3_CORNER_OFFSET)
|
|
* @selector: Pointer which is filled with the selector value for the
|
|
* corner
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device. The VDD voltage will not be
|
|
* physically configured until both this function and cpr3_regulator_enable()
|
|
* are called.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_set_voltage(struct regulator_dev *rdev,
|
|
int corner, int corner_max, unsigned *selector)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
struct cpr3_controller *ctrl = vreg->thread->ctrl;
|
|
int rc = 0;
|
|
int last_corner;
|
|
|
|
corner -= CPR3_CORNER_OFFSET;
|
|
corner_max -= CPR3_CORNER_OFFSET;
|
|
*selector = corner;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (!vreg->vreg_enabled) {
|
|
vreg->current_corner = corner;
|
|
cpr3_debug(vreg, "stored corner=%d\n", corner);
|
|
goto done;
|
|
} else if (vreg->current_corner == corner) {
|
|
goto done;
|
|
}
|
|
|
|
last_corner = vreg->current_corner;
|
|
vreg->current_corner = corner;
|
|
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
|
|
vreg->current_corner = last_corner;
|
|
}
|
|
|
|
cpr3_debug(vreg, "set corner=%d\n", corner);
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_get_voltage() - get the voltage corner for the CPR3 regulator
|
|
* associated with the regulator device
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: voltage corner value offset by CPR3_CORNER_OFFSET
|
|
*/
|
|
static int cpr3_regulator_get_voltage(struct regulator_dev *rdev)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
|
|
if (vreg->current_corner == CPR3_REGULATOR_CORNER_INVALID)
|
|
return CPR3_CORNER_OFFSET;
|
|
else
|
|
return vreg->current_corner + CPR3_CORNER_OFFSET;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_list_voltage() - return the voltage corner mapped to the
|
|
* specified selector
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
* @selector: Regulator selector
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: voltage corner value offset by CPR3_CORNER_OFFSET
|
|
*/
|
|
static int cpr3_regulator_list_voltage(struct regulator_dev *rdev,
|
|
unsigned selector)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
|
|
if (selector < vreg->corner_count)
|
|
return selector + CPR3_CORNER_OFFSET;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_list_corner_voltage() - return the ceiling voltage mapped to
|
|
* the specified voltage corner
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
* @corner: Voltage corner
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: voltage value in microvolts or -EINVAL if the corner is out of range
|
|
*/
|
|
static int cpr3_regulator_list_corner_voltage(struct regulator_dev *rdev,
|
|
int corner)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
|
|
corner -= CPR3_CORNER_OFFSET;
|
|
|
|
if (corner >= 0 && corner < vreg->corner_count)
|
|
return vreg->corner[corner].ceiling_volt;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_is_enabled() - return the enable state of the CPR3 regulator
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: true if regulator is enabled, false if regulator is disabled
|
|
*/
|
|
static int cpr3_regulator_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
|
|
return vreg->vreg_enabled;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_enable() - enable the CPR3 regulator
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
struct cpr3_controller *ctrl = vreg->thread->ctrl;
|
|
int rc = 0;
|
|
|
|
if (vreg->vreg_enabled == true)
|
|
return 0;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (ctrl->system_regulator) {
|
|
rc = regulator_enable(ctrl->system_regulator);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "regulator_enable(system) failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
rc = regulator_enable(ctrl->vdd_regulator);
|
|
if (rc) {
|
|
cpr3_err(vreg, "regulator_enable(vdd) failed, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
|
|
if (vreg->ldo_regulator) {
|
|
rc = regulator_enable(vreg->ldo_regulator);
|
|
if (rc) {
|
|
cpr3_err(vreg, "regulator_enable(ldo) failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
vreg->vreg_enabled = true;
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
|
|
regulator_disable(ctrl->vdd_regulator);
|
|
vreg->vreg_enabled = false;
|
|
goto done;
|
|
}
|
|
|
|
cpr3_debug(vreg, "Enabled\n");
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_disable() - disable the CPR3 regulator
|
|
* @rdev: Regulator device pointer for the cpr3-regulator
|
|
*
|
|
* This function is passed as a callback function into the regulator ops that
|
|
* are registered for each cpr3-regulator device.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
|
|
struct cpr3_controller *ctrl = vreg->thread->ctrl;
|
|
int rc, rc2;
|
|
|
|
if (vreg->vreg_enabled == false)
|
|
return 0;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (vreg->ldo_regulator && vreg->ldo_regulator_bypass == LDO_MODE) {
|
|
rc = regulator_get_voltage(ctrl->vdd_regulator);
|
|
if (rc < 0) {
|
|
cpr3_err(vreg, "regulator_get_voltage(vdd) failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
|
|
/* Switch back to BHS for safe operation */
|
|
rc = cpr3_regulator_set_bhs_mode(vreg, rc,
|
|
ctrl->aggr_corner.ceiling_volt);
|
|
if (rc) {
|
|
cpr3_err(vreg, "unable to switch to BHS mode, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (vreg->ldo_regulator) {
|
|
rc = regulator_disable(vreg->ldo_regulator);
|
|
if (rc) {
|
|
cpr3_err(vreg, "regulator_disable(ldo) failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
rc = regulator_disable(ctrl->vdd_regulator);
|
|
if (rc) {
|
|
cpr3_err(vreg, "regulator_disable(vdd) failed, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
|
|
vreg->vreg_enabled = false;
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
|
|
rc2 = regulator_enable(ctrl->vdd_regulator);
|
|
vreg->vreg_enabled = true;
|
|
goto done;
|
|
}
|
|
|
|
if (ctrl->system_regulator) {
|
|
rc = regulator_disable(ctrl->system_regulator);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "regulator_disable(system) failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
cpr3_debug(vreg, "Disabled\n");
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct regulator_ops cpr3_regulator_ops = {
|
|
.enable = cpr3_regulator_enable,
|
|
.disable = cpr3_regulator_disable,
|
|
.is_enabled = cpr3_regulator_is_enabled,
|
|
.set_voltage = cpr3_regulator_set_voltage,
|
|
.get_voltage = cpr3_regulator_get_voltage,
|
|
.list_voltage = cpr3_regulator_list_voltage,
|
|
.list_corner_voltage = cpr3_regulator_list_corner_voltage,
|
|
};
|
|
|
|
/**
|
|
* cpr3_print_result() - print CPR measurement results to the kernel log for
|
|
* debugging purposes
|
|
* @thread: Pointer to the CPR3 thread
|
|
*
|
|
* Return: None
|
|
*/
|
|
static void cpr3_print_result(struct cpr3_thread *thread)
|
|
{
|
|
struct cpr3_controller *ctrl = thread->ctrl;
|
|
u32 result[3], busy, step_dn, step_up, error_steps, error, negative;
|
|
u32 quot_min, quot_max, ro_min, ro_max, step_quot_min, step_quot_max;
|
|
u32 sensor_min, sensor_max;
|
|
char *sign;
|
|
|
|
result[0] = cpr3_read(ctrl, CPR3_REG_RESULT0(thread->thread_id));
|
|
result[1] = cpr3_read(ctrl, CPR3_REG_RESULT1(thread->thread_id));
|
|
result[2] = cpr3_read(ctrl, CPR3_REG_RESULT2(thread->thread_id));
|
|
|
|
busy = !!(result[0] & CPR3_RESULT0_BUSY_MASK);
|
|
step_dn = !!(result[0] & CPR3_RESULT0_STEP_DN_MASK);
|
|
step_up = !!(result[0] & CPR3_RESULT0_STEP_UP_MASK);
|
|
error_steps = (result[0] & CPR3_RESULT0_ERROR_STEPS_MASK)
|
|
>> CPR3_RESULT0_ERROR_STEPS_SHIFT;
|
|
error = (result[0] & CPR3_RESULT0_ERROR_MASK)
|
|
>> CPR3_RESULT0_ERROR_SHIFT;
|
|
negative = !!(result[0] & CPR3_RESULT0_NEGATIVE_MASK);
|
|
|
|
quot_min = (result[1] & CPR3_RESULT1_QUOT_MIN_MASK)
|
|
>> CPR3_RESULT1_QUOT_MIN_SHIFT;
|
|
quot_max = (result[1] & CPR3_RESULT1_QUOT_MAX_MASK)
|
|
>> CPR3_RESULT1_QUOT_MAX_SHIFT;
|
|
ro_min = (result[1] & CPR3_RESULT1_RO_MIN_MASK)
|
|
>> CPR3_RESULT1_RO_MIN_SHIFT;
|
|
ro_max = (result[1] & CPR3_RESULT1_RO_MAX_MASK)
|
|
>> CPR3_RESULT1_RO_MAX_SHIFT;
|
|
|
|
step_quot_min = (result[2] & CPR3_RESULT2_STEP_QUOT_MIN_MASK)
|
|
>> CPR3_RESULT2_STEP_QUOT_MIN_SHIFT;
|
|
step_quot_max = (result[2] & CPR3_RESULT2_STEP_QUOT_MAX_MASK)
|
|
>> CPR3_RESULT2_STEP_QUOT_MAX_SHIFT;
|
|
sensor_min = (result[2] & CPR3_RESULT2_SENSOR_MIN_MASK)
|
|
>> CPR3_RESULT2_SENSOR_MIN_SHIFT;
|
|
sensor_max = (result[2] & CPR3_RESULT2_SENSOR_MAX_MASK)
|
|
>> CPR3_RESULT2_SENSOR_MAX_SHIFT;
|
|
|
|
sign = negative ? "-" : "";
|
|
cpr3_debug(ctrl, "thread %u: busy=%u, step_dn=%u, step_up=%u, error_steps=%s%u, error=%s%u\n",
|
|
thread->thread_id, busy, step_dn, step_up, sign, error_steps,
|
|
sign, error);
|
|
cpr3_debug(ctrl, "thread %u: quot_min=%u, quot_max=%u, ro_min=%u, ro_max=%u\n",
|
|
thread->thread_id, quot_min, quot_max, ro_min, ro_max);
|
|
cpr3_debug(ctrl, "thread %u: step_quot_min=%u, step_quot_max=%u, sensor_min=%u, sensor_max=%u\n",
|
|
thread->thread_id, step_quot_min, step_quot_max, sensor_min,
|
|
sensor_max);
|
|
}
|
|
|
|
/**
|
|
* cpr3_thread_busy() - returns if the specified CPR3 thread is busy taking
|
|
* a measurement
|
|
* @thread: Pointer to the CPR3 thread
|
|
*
|
|
* Return: CPR3 busy status
|
|
*/
|
|
static bool cpr3_thread_busy(struct cpr3_thread *thread)
|
|
{
|
|
u32 result;
|
|
|
|
result = cpr3_read(thread->ctrl, CPR3_REG_RESULT0(thread->thread_id));
|
|
|
|
return !!(result & CPR3_RESULT0_BUSY_MASK);
|
|
}
|
|
|
|
/**
|
|
* cpr3_irq_handler() - CPR interrupt handler callback function used for
|
|
* software closed-loop operation
|
|
* @irq: CPR interrupt number
|
|
* @data: Private data corresponding to the CPR3 controller
|
|
* pointer
|
|
*
|
|
* This function increases or decreases the vdd supply voltage based upon the
|
|
* CPR controller recommendation.
|
|
*
|
|
* Return: IRQ_HANDLED
|
|
*/
|
|
static irqreturn_t cpr3_irq_handler(int irq, void *data)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
struct cpr3_corner *aggr = &ctrl->aggr_corner;
|
|
u32 cont = CPR3_CONT_CMD_NACK;
|
|
u32 reg_last_measurement = 0;
|
|
struct cpr3_regulator *vreg;
|
|
struct cpr3_corner *corner;
|
|
unsigned long flags;
|
|
int i, j, new_volt, last_volt, dynamic_floor_volt, rc;
|
|
u32 irq_en, status, cpr_status, ctl;
|
|
bool up, down;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (!ctrl->cpr_enabled) {
|
|
cpr3_debug(ctrl, "CPR interrupt received but CPR is disabled\n");
|
|
mutex_unlock(&ctrl->lock);
|
|
return IRQ_HANDLED;
|
|
} else if (ctrl->use_hw_closed_loop) {
|
|
cpr3_debug(ctrl, "CPR interrupt received but CPR is using HW closed-loop\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* CPR IRQ status checking and CPR controller disabling must happen
|
|
* atomically and without invening delay in order to avoid an interrupt
|
|
* storm caused by the handler racing with the CPR controller.
|
|
*/
|
|
local_irq_save(flags);
|
|
preempt_disable();
|
|
|
|
status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
|
|
up = status & CPR3_IRQ_UP;
|
|
down = status & CPR3_IRQ_DOWN;
|
|
|
|
if (!up && !down) {
|
|
/*
|
|
* Toggle the CPR controller off and then back on since the
|
|
* hardware and software states are out of sync. This condition
|
|
* occurs after an aging measurement completes as the CPR IRQ
|
|
* physically triggers during the aging measurement but the
|
|
* handler is stuck waiting on the mutex lock.
|
|
*/
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
local_irq_restore(flags);
|
|
preempt_enable();
|
|
|
|
/* Wait for the loop disable write to complete */
|
|
mb();
|
|
|
|
/* Wait for BUSY=1 and LOOP_EN=0 in CPR controller registers. */
|
|
for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
|
|
cpr_status = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
|
|
ctl = cpr3_read(ctrl, CPR3_REG_CPR_CTL);
|
|
if (cpr_status & CPR3_CPR_STATUS_BUSY_MASK
|
|
&& (ctl & CPR3_CPR_CTL_LOOP_EN_MASK)
|
|
== CPR3_CPR_CTL_LOOP_DISABLE)
|
|
break;
|
|
udelay(10);
|
|
}
|
|
if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
|
|
cpr3_debug(ctrl, "CPR controller not disabled after %d us\n",
|
|
CPR3_REGISTER_WRITE_DELAY_US);
|
|
|
|
/* Clear interrupt status */
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
|
|
CPR3_IRQ_UP | CPR3_IRQ_DOWN);
|
|
|
|
/* Wait for the interrupt clearing write to complete */
|
|
mb();
|
|
|
|
/* Wait for IRQ_STATUS register to be cleared. */
|
|
for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
|
|
status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
|
|
if (!(status & (CPR3_IRQ_UP | CPR3_IRQ_DOWN)))
|
|
break;
|
|
udelay(10);
|
|
}
|
|
if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
|
|
cpr3_debug(ctrl, "CPR interrupts not cleared after %d us\n",
|
|
CPR3_REGISTER_WRITE_DELAY_US);
|
|
|
|
cpr3_ctrl_loop_enable(ctrl);
|
|
|
|
cpr3_debug(ctrl, "CPR interrupt received but no up or down status bit is set\n");
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
return IRQ_HANDLED;
|
|
} else if (up && down) {
|
|
cpr3_debug(ctrl, "both up and down status bits set\n");
|
|
/* The up flag takes precedence over the down flag. */
|
|
down = false;
|
|
}
|
|
|
|
if (ctrl->supports_hw_closed_loop)
|
|
reg_last_measurement
|
|
= cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
|
|
dynamic_floor_volt = cpr3_regulator_get_dynamic_floor_volt(ctrl,
|
|
reg_last_measurement);
|
|
|
|
local_irq_restore(flags);
|
|
preempt_enable();
|
|
|
|
irq_en = aggr->irq_en;
|
|
last_volt = aggr->last_volt;
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
if (cpr3_thread_busy(&ctrl->thread[i])) {
|
|
cpr3_debug(ctrl, "CPR thread %u busy when it should be waiting for SW cont\n",
|
|
ctrl->thread[i].thread_id);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
new_volt = up ? last_volt + ctrl->step_volt
|
|
: last_volt - ctrl->step_volt;
|
|
|
|
/* Re-enable UP/DOWN interrupt when its opposite is received. */
|
|
irq_en |= up ? CPR3_IRQ_DOWN : CPR3_IRQ_UP;
|
|
|
|
if (new_volt > aggr->ceiling_volt) {
|
|
new_volt = aggr->ceiling_volt;
|
|
irq_en &= ~CPR3_IRQ_UP;
|
|
cpr3_debug(ctrl, "limiting to ceiling=%d uV\n",
|
|
aggr->ceiling_volt);
|
|
} else if (new_volt < aggr->floor_volt) {
|
|
new_volt = aggr->floor_volt;
|
|
irq_en &= ~CPR3_IRQ_DOWN;
|
|
cpr3_debug(ctrl, "limiting to floor=%d uV\n", aggr->floor_volt);
|
|
}
|
|
|
|
if (down && new_volt < dynamic_floor_volt) {
|
|
/*
|
|
* The vdd-supply voltage should not be decreased below the
|
|
* dynamic floor voltage. However, it is not necessary (and
|
|
* counter productive) to force the voltage up to this level
|
|
* if it happened to be below it since the closed-loop voltage
|
|
* must have gotten there in a safe manner while the power
|
|
* domains for the CPR3 regulator imposing the dynamic floor
|
|
* were not bypassed.
|
|
*/
|
|
new_volt = last_volt;
|
|
irq_en &= ~CPR3_IRQ_DOWN;
|
|
cpr3_debug(ctrl, "limiting to dynamic floor=%d uV\n",
|
|
dynamic_floor_volt);
|
|
}
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++)
|
|
cpr3_print_result(&ctrl->thread[i]);
|
|
|
|
cpr3_debug(ctrl, "%s: new_volt=%d uV, last_volt=%d uV\n",
|
|
up ? "UP" : "DN", new_volt, last_volt);
|
|
|
|
if (ctrl->proc_clock_throttle && last_volt == aggr->ceiling_volt
|
|
&& new_volt < last_volt)
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
ctrl->proc_clock_throttle);
|
|
|
|
if (new_volt != last_volt) {
|
|
rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
|
|
last_volt,
|
|
aggr);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "scale_vdd() failed to set vdd=%d uV, rc=%d\n",
|
|
new_volt, rc);
|
|
goto done;
|
|
}
|
|
cont = CPR3_CONT_CMD_ACK;
|
|
|
|
/*
|
|
* Update the closed-loop voltage for all regulators managed
|
|
* by this CPR controller.
|
|
*/
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
cpr3_update_vreg_closed_loop_volt(vreg,
|
|
new_volt, reg_last_measurement);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctrl->proc_clock_throttle && new_volt == aggr->ceiling_volt)
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
CPR3_PD_THROTTLE_DISABLE);
|
|
|
|
corner = &ctrl->thread[0].vreg[0].corner[
|
|
ctrl->thread[0].vreg[0].current_corner];
|
|
|
|
if (irq_en != aggr->irq_en) {
|
|
aggr->irq_en = irq_en;
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_en);
|
|
}
|
|
|
|
aggr->last_volt = new_volt;
|
|
|
|
done:
|
|
/* Clear interrupt status */
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR, CPR3_IRQ_UP | CPR3_IRQ_DOWN);
|
|
|
|
/* ACK or NACK the CPR controller */
|
|
cpr3_write(ctrl, CPR3_REG_CONT_CMD, cont);
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* cpr3_ceiling_irq_handler() - CPR ceiling reached interrupt handler callback
|
|
* function used for hardware closed-loop operation
|
|
* @irq: CPR ceiling interrupt number
|
|
* @data: Private data corresponding to the CPR3 controller
|
|
* pointer
|
|
*
|
|
* This function disables processor clock throttling and closed-loop operation
|
|
* when the ceiling voltage is reached.
|
|
*
|
|
* Return: IRQ_HANDLED
|
|
*/
|
|
static irqreturn_t cpr3_ceiling_irq_handler(int irq, void *data)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
int rc, volt;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (!ctrl->cpr_enabled) {
|
|
cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is disabled\n");
|
|
goto done;
|
|
} else if (!ctrl->use_hw_closed_loop) {
|
|
cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is using SW closed-loop\n");
|
|
goto done;
|
|
}
|
|
|
|
volt = regulator_get_voltage(ctrl->vdd_regulator);
|
|
if (volt < 0) {
|
|
cpr3_err(ctrl, "could not get vdd voltage, rc=%d\n", volt);
|
|
goto done;
|
|
} else if (volt != ctrl->aggr_corner.ceiling_volt) {
|
|
cpr3_debug(ctrl, "CPR ceiling interrupt received but vdd voltage: %d uV != ceiling voltage: %d uV\n",
|
|
volt, ctrl->aggr_corner.ceiling_volt);
|
|
goto done;
|
|
}
|
|
|
|
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
|
|
/*
|
|
* Since the ceiling voltage has been reached, disable processor
|
|
* clock throttling as well as CPR closed-loop operation.
|
|
*/
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
CPR3_PD_THROTTLE_DISABLE);
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
cpr3_debug(ctrl, "CPR closed-loop and throttling disabled\n");
|
|
}
|
|
|
|
done:
|
|
rc = msm_spm_avs_clear_irq(0, MSM_SPM_AVS_IRQ_MAX);
|
|
if (rc)
|
|
cpr3_err(ctrl, "could not clear max IRQ, rc=%d\n", rc);
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_vreg_register() - register a regulator device for a CPR3
|
|
* regulator
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
*
|
|
* This function initializes all regulator framework related structures and then
|
|
* calls regulator_register() for the CPR3 regulator.
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_vreg_register(struct cpr3_regulator *vreg)
|
|
{
|
|
struct regulator_config config = {};
|
|
struct regulator_desc *rdesc;
|
|
struct regulator_init_data *init_data;
|
|
int rc;
|
|
|
|
init_data = of_get_regulator_init_data(vreg->thread->ctrl->dev,
|
|
vreg->of_node);
|
|
if (!init_data) {
|
|
cpr3_err(vreg, "regulator init data is missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
init_data->constraints.input_uV = init_data->constraints.max_uV;
|
|
init_data->constraints.valid_ops_mask
|
|
|= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
|
|
|
|
rdesc = &vreg->rdesc;
|
|
rdesc->n_voltages = vreg->corner_count;
|
|
rdesc->name = init_data->constraints.name;
|
|
rdesc->ops = &cpr3_regulator_ops;
|
|
rdesc->owner = THIS_MODULE;
|
|
rdesc->type = REGULATOR_VOLTAGE;
|
|
|
|
config.dev = vreg->thread->ctrl->dev;
|
|
config.driver_data = vreg;
|
|
config.init_data = init_data;
|
|
config.of_node = vreg->of_node;
|
|
|
|
vreg->rdev = regulator_register(rdesc, &config);
|
|
if (IS_ERR(vreg->rdev)) {
|
|
rc = PTR_ERR(vreg->rdev);
|
|
cpr3_err(vreg, "regulator_register failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_int_set(void *data, u64 val)
|
|
{
|
|
*(int *)data = val;
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_int_get(void *data, u64 *val)
|
|
{
|
|
*val = *(int *)data;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_int, debugfs_int_get, debugfs_int_set, "%lld\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_int_ro, debugfs_int_get, NULL, "%lld\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_int_wo, NULL, debugfs_int_set, "%lld\n");
|
|
|
|
/**
|
|
* debugfs_create_int - create a debugfs file that is used to read and write a
|
|
* signed int value
|
|
* @name: Pointer to a string containing the name of the file to
|
|
* create
|
|
* @mode: The permissions that the file should have
|
|
* @parent: Pointer to the parent dentry for this file. This should
|
|
* be a directory dentry if set. If this parameter is
|
|
* %NULL, then the file will be created in the root of the
|
|
* debugfs filesystem.
|
|
* @value: Pointer to the variable that the file should read to and
|
|
* write from
|
|
*
|
|
* This function creates a file in debugfs with the given name that
|
|
* contains the value of the variable @value. If the @mode variable is so
|
|
* set, it can be read from, and written to.
|
|
*
|
|
* This function will return a pointer to a dentry if it succeeds. This
|
|
* pointer must be passed to the debugfs_remove() function when the file is
|
|
* to be removed. If an error occurs, %NULL will be returned.
|
|
*/
|
|
static struct dentry *debugfs_create_int(const char *name, umode_t mode,
|
|
struct dentry *parent, int *value)
|
|
{
|
|
/* if there are no write bits set, make read only */
|
|
if (!(mode & S_IWUGO))
|
|
return debugfs_create_file(name, mode, parent, value,
|
|
&fops_int_ro);
|
|
/* if there are no read bits set, make write only */
|
|
if (!(mode & S_IRUGO))
|
|
return debugfs_create_file(name, mode, parent, value,
|
|
&fops_int_wo);
|
|
|
|
return debugfs_create_file(name, mode, parent, value, &fops_int);
|
|
}
|
|
|
|
static int debugfs_bool_get(void *data, u64 *val)
|
|
{
|
|
*val = *(bool *)data;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_bool_ro, debugfs_bool_get, NULL, "%lld\n");
|
|
|
|
/**
|
|
* cpr3_debug_ldo_mode_allowed_set() - debugfs callback used to change the
|
|
* value of the CPR3 regulator ldo_mode_allowed flag
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: New value for ldo_mode_allowed
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_ldo_mode_allowed_set(void *data, u64 val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
struct cpr3_controller *ctrl = vreg->thread->ctrl;
|
|
bool allow = !!val;
|
|
int rc, vdd_volt;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (vreg->ldo_mode_allowed == allow)
|
|
goto done;
|
|
|
|
vreg->ldo_mode_allowed = allow;
|
|
|
|
if (!allow && vreg->ldo_regulator_bypass == LDO_MODE) {
|
|
vdd_volt = regulator_get_voltage(ctrl->vdd_regulator);
|
|
if (vdd_volt < 0) {
|
|
cpr3_err(vreg, "regulator_get_voltage(vdd) failed, rc=%d\n",
|
|
vdd_volt);
|
|
goto done;
|
|
}
|
|
|
|
/* Switch back to BHS */
|
|
rc = cpr3_regulator_set_bhs_mode(vreg, vdd_volt,
|
|
ctrl->aggr_corner.ceiling_volt);
|
|
if (rc) {
|
|
cpr3_err(vreg, "unable to switch to BHS mode, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
} else {
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(vreg, "could not change LDO mode=%s, rc=%d\n",
|
|
allow ? "allowed" : "disallowed", rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
cpr3_debug(vreg, "LDO mode=%s\n", allow ? "allowed" : "disallowed");
|
|
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_ldo_mode_allowed_get() - debugfs callback used to retrieve the
|
|
* value of the CPR3 regulator ldo_mode_allowed flag
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: Output parameter written with a value of the
|
|
* ldo_mode_allowed flag
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_ldo_mode_allowed_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
|
|
*val = vreg->ldo_mode_allowed;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_ldo_mode_allowed_fops,
|
|
cpr3_debug_ldo_mode_allowed_get,
|
|
cpr3_debug_ldo_mode_allowed_set,
|
|
"%llu\n");
|
|
|
|
/**
|
|
* cpr3_debug_ldo_mode_get() - debugfs callback used to retrieve the state of
|
|
* the CPR3 regulator's LDO
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: Output parameter written with a value of 1 if using
|
|
* LDO mode or 0 if the LDO is bypassed
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_ldo_mode_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
|
|
*val = (vreg->ldo_regulator_bypass == LDO_MODE);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_ldo_mode_fops, cpr3_debug_ldo_mode_get,
|
|
NULL, "%llu\n");
|
|
|
|
/**
|
|
* struct cpr3_debug_corner_info - data structure used by the
|
|
* cpr3_debugfs_create_corner_int function
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
* @index: Pointer to the corner array index
|
|
* @member_offset: Offset in bytes from the beginning of struct cpr3_corner
|
|
* to the beginning of the value to be read from
|
|
* @corner: Pointer to the CPR3 corner array
|
|
*/
|
|
struct cpr3_debug_corner_info {
|
|
struct cpr3_regulator *vreg;
|
|
int *index;
|
|
size_t member_offset;
|
|
struct cpr3_corner *corner;
|
|
};
|
|
|
|
static int cpr3_debug_corner_int_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_debug_corner_info *info = data;
|
|
struct cpr3_controller *ctrl = info->vreg->thread->ctrl;
|
|
int i;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
i = *info->index;
|
|
if (i < 0)
|
|
i = 0;
|
|
|
|
*val = *(int *)((char *)&info->vreg->corner[i] + info->member_offset);
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_int_fops, cpr3_debug_corner_int_get,
|
|
NULL, "%lld\n");
|
|
|
|
/**
|
|
* cpr3_debugfs_create_corner_int - create a debugfs file that is used to read
|
|
* a signed int value out of a CPR3 regulator's corner array
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
* @name: Pointer to a string containing the name of the file to
|
|
* create
|
|
* @mode: The permissions that the file should have
|
|
* @parent: Pointer to the parent dentry for this file. This should
|
|
* be a directory dentry if set. If this parameter is
|
|
* %NULL, then the file will be created in the root of the
|
|
* debugfs filesystem.
|
|
* @index: Pointer to the corner array index
|
|
* @member_offset: Offset in bytes from the beginning of struct cpr3_corner
|
|
* to the beginning of the value to be read from
|
|
*
|
|
* This function creates a file in debugfs with the given name that
|
|
* contains the value of the int type variable vreg->corner[index].member
|
|
* where member_offset == offsetof(struct cpr3_corner, member).
|
|
*/
|
|
static struct dentry *cpr3_debugfs_create_corner_int(
|
|
struct cpr3_regulator *vreg, const char *name, umode_t mode,
|
|
struct dentry *parent, int *index, size_t member_offset)
|
|
{
|
|
struct cpr3_debug_corner_info *info;
|
|
|
|
info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return NULL;
|
|
|
|
info->vreg = vreg;
|
|
info->index = index;
|
|
info->member_offset = member_offset;
|
|
|
|
return debugfs_create_file(name, mode, parent, info,
|
|
&cpr3_debug_corner_int_fops);
|
|
}
|
|
|
|
static int cpr3_debug_quot_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct cpr3_debug_corner_info *info = inode->i_private;
|
|
struct cpr3_thread *thread = info->vreg->thread;
|
|
int size, i, pos;
|
|
u32 *quot;
|
|
char *buf;
|
|
|
|
/*
|
|
* Max size:
|
|
* - 10 digits + ' ' or '\n' = 11 bytes per number
|
|
* - terminating '\0'
|
|
*/
|
|
size = CPR3_RO_COUNT * 11;
|
|
buf = kzalloc(size + 1, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
file->private_data = buf;
|
|
|
|
mutex_lock(&thread->ctrl->lock);
|
|
|
|
quot = info->corner[*info->index].target_quot;
|
|
|
|
for (i = 0, pos = 0; i < CPR3_RO_COUNT; i++)
|
|
pos += scnprintf(buf + pos, size - pos, "%u%c",
|
|
quot[i], i < CPR3_RO_COUNT - 1 ? ' ' : '\n');
|
|
|
|
mutex_unlock(&thread->ctrl->lock);
|
|
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static ssize_t cpr3_debug_quot_read(struct file *file, char __user *buf,
|
|
size_t len, loff_t *ppos)
|
|
{
|
|
return simple_read_from_buffer(buf, len, ppos, file->private_data,
|
|
strlen(file->private_data));
|
|
}
|
|
|
|
static int cpr3_debug_quot_release(struct inode *inode, struct file *file)
|
|
{
|
|
kfree(file->private_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations cpr3_debug_quot_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = cpr3_debug_quot_open,
|
|
.release = cpr3_debug_quot_release,
|
|
.read = cpr3_debug_quot_read,
|
|
.llseek = no_llseek,
|
|
};
|
|
|
|
/**
|
|
* cpr3_regulator_debugfs_corner_add() - add debugfs files to expose
|
|
* configuration data for the CPR corner
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
* @corner_dir: Pointer to the parent corner dentry for the new files
|
|
* @index: Pointer to the corner array index
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_debugfs_corner_add(struct cpr3_regulator *vreg,
|
|
struct dentry *corner_dir, int *index)
|
|
{
|
|
struct cpr3_debug_corner_info *info;
|
|
struct dentry *temp;
|
|
|
|
temp = cpr3_debugfs_create_corner_int(vreg, "floor_volt", S_IRUGO,
|
|
corner_dir, index, offsetof(struct cpr3_corner, floor_volt));
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "floor_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = cpr3_debugfs_create_corner_int(vreg, "ceiling_volt", S_IRUGO,
|
|
corner_dir, index, offsetof(struct cpr3_corner, ceiling_volt));
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "ceiling_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = cpr3_debugfs_create_corner_int(vreg, "open_loop_volt", S_IRUGO,
|
|
corner_dir, index,
|
|
offsetof(struct cpr3_corner, open_loop_volt));
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "open_loop_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = cpr3_debugfs_create_corner_int(vreg, "last_volt", S_IRUGO,
|
|
corner_dir, index, offsetof(struct cpr3_corner, last_volt));
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "last_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return;
|
|
|
|
info->vreg = vreg;
|
|
info->index = index;
|
|
info->corner = vreg->corner;
|
|
|
|
temp = debugfs_create_file("target_quots", S_IRUGO, corner_dir,
|
|
info, &cpr3_debug_quot_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "target_quots debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_corner_index_set() - debugfs callback used to change the
|
|
* value of the CPR3 regulator debug_corner index
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: New value for debug_corner
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_corner_index_set(void *data, u64 val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
|
|
if (val < CPR3_CORNER_OFFSET || val > vreg->corner_count) {
|
|
cpr3_err(vreg, "invalid corner index %llu; allowed values: %d-%d\n",
|
|
val, CPR3_CORNER_OFFSET, vreg->corner_count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&vreg->thread->ctrl->lock);
|
|
vreg->debug_corner = val - CPR3_CORNER_OFFSET;
|
|
mutex_unlock(&vreg->thread->ctrl->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_corner_index_get() - debugfs callback used to retrieve
|
|
* the value of the CPR3 regulator debug_corner index
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: Output parameter written with the value of
|
|
* debug_corner
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_corner_index_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
|
|
*val = vreg->debug_corner + CPR3_CORNER_OFFSET;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_index_fops,
|
|
cpr3_debug_corner_index_get,
|
|
cpr3_debug_corner_index_set,
|
|
"%llu\n");
|
|
|
|
/**
|
|
* cpr3_debug_current_corner_index_get() - debugfs callback used to retrieve
|
|
* the value of the CPR3 regulator current_corner index
|
|
* @data: Pointer to private data which is equal to the CPR3
|
|
* regulator pointer
|
|
* @val: Output parameter written with the value of
|
|
* current_corner
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_current_corner_index_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_regulator *vreg = data;
|
|
|
|
*val = vreg->current_corner + CPR3_CORNER_OFFSET;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_current_corner_index_fops,
|
|
cpr3_debug_current_corner_index_get,
|
|
NULL, "%llu\n");
|
|
|
|
/**
|
|
* cpr3_regulator_debugfs_vreg_add() - add debugfs files to expose configuration
|
|
* data for the CPR3 regulator
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
* @thread_dir CPR3 thread debugfs directory handle
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_debugfs_vreg_add(struct cpr3_regulator *vreg,
|
|
struct dentry *thread_dir)
|
|
{
|
|
struct dentry *temp, *corner_dir, *vreg_dir;
|
|
|
|
vreg_dir = debugfs_create_dir(vreg->name, thread_dir);
|
|
if (IS_ERR_OR_NULL(vreg_dir)) {
|
|
cpr3_err(vreg, "%s debugfs directory creation failed\n",
|
|
vreg->name);
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("speed_bin_fuse", S_IRUGO, vreg_dir,
|
|
&vreg->speed_bin_fuse);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "speed_bin_fuse debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("cpr_rev_fuse", S_IRUGO, vreg_dir,
|
|
&vreg->cpr_rev_fuse);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "cpr_rev_fuse debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("fuse_combo", S_IRUGO, vreg_dir,
|
|
&vreg->fuse_combo);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "fuse_combo debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
if (vreg->ldo_regulator) {
|
|
temp = debugfs_create_file("ldo_mode", S_IRUGO, vreg_dir,
|
|
vreg, &cpr3_debug_ldo_mode_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "ldo_mode debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("ldo_mode_allowed",
|
|
S_IRUGO | S_IWUSR, vreg_dir, vreg,
|
|
&cpr3_debug_ldo_mode_allowed_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "ldo_mode_allowed debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
temp = debugfs_create_int("corner_count", S_IRUGO, vreg_dir,
|
|
&vreg->corner_count);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "corner_count debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
corner_dir = debugfs_create_dir("corner", vreg_dir);
|
|
if (IS_ERR_OR_NULL(corner_dir)) {
|
|
cpr3_err(vreg, "corner debugfs directory creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("index", S_IRUGO | S_IWUSR, corner_dir,
|
|
vreg, &cpr3_debug_corner_index_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "index debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
|
|
&vreg->debug_corner);
|
|
|
|
corner_dir = debugfs_create_dir("current_corner", vreg_dir);
|
|
if (IS_ERR_OR_NULL(corner_dir)) {
|
|
cpr3_err(vreg, "current_corner debugfs directory creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("index", S_IRUGO, corner_dir,
|
|
vreg, &cpr3_debug_current_corner_index_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(vreg, "index debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
|
|
&vreg->current_corner);
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_debugfs_thread_add() - add debugfs files to expose
|
|
* configuration data for the CPR thread
|
|
* @thread: Pointer to the CPR3 thread
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_debugfs_thread_add(struct cpr3_thread *thread)
|
|
{
|
|
struct cpr3_controller *ctrl = thread->ctrl;
|
|
struct dentry *aggr_dir, *temp, *thread_dir;
|
|
struct cpr3_debug_corner_info *info;
|
|
char buf[20];
|
|
int *index;
|
|
int i;
|
|
|
|
scnprintf(buf, sizeof(buf), "thread%u", thread->thread_id);
|
|
thread_dir = debugfs_create_dir(buf, thread->ctrl->debugfs);
|
|
if (IS_ERR_OR_NULL(thread_dir)) {
|
|
cpr3_err(ctrl, "thread %u %s debugfs directory creation failed\n",
|
|
thread->thread_id, buf);
|
|
return;
|
|
}
|
|
|
|
aggr_dir = debugfs_create_dir("max_aggregated_params", thread_dir);
|
|
if (IS_ERR_OR_NULL(aggr_dir)) {
|
|
cpr3_err(ctrl, "thread %u max_aggregated_params debugfs directory creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
|
|
&thread->aggr_corner.floor_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread %u aggr floor_volt debugfs file creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
|
|
&thread->aggr_corner.ceiling_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread %u aggr ceiling_volt debugfs file creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
|
|
&thread->aggr_corner.open_loop_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread %u aggr open_loop_volt debugfs file creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
|
|
&thread->aggr_corner.last_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread %u aggr last_volt debugfs file creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
info = devm_kzalloc(thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
|
|
index = devm_kzalloc(thread->ctrl->dev, sizeof(*index), GFP_KERNEL);
|
|
if (!info || !index)
|
|
return;
|
|
*index = 0;
|
|
info->vreg = &thread->vreg[0];
|
|
info->index = index;
|
|
info->corner = &thread->aggr_corner;
|
|
|
|
temp = debugfs_create_file("target_quots", S_IRUGO, aggr_dir,
|
|
info, &cpr3_debug_quot_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread %u target_quots debugfs file creation failed\n",
|
|
thread->thread_id);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < thread->vreg_count; i++)
|
|
cpr3_regulator_debugfs_vreg_add(&thread->vreg[i], thread_dir);
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_closed_loop_enable_set() - debugfs callback used to change the
|
|
* value of the CPR controller cpr_allowed_sw flag which enables or
|
|
* disables closed-loop operation
|
|
* @data: Pointer to private data which is equal to the CPR
|
|
* controller pointer
|
|
* @val: New value for cpr_allowed_sw
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_closed_loop_enable_set(void *data, u64 val)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
bool enable = !!val;
|
|
int rc;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (ctrl->cpr_allowed_sw == enable)
|
|
goto done;
|
|
|
|
if (enable && !ctrl->cpr_allowed_hw) {
|
|
cpr3_err(ctrl, "CPR closed-loop operation is not allowed\n");
|
|
goto done;
|
|
}
|
|
|
|
ctrl->cpr_allowed_sw = enable;
|
|
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not change CPR enable state=%u, rc=%d\n",
|
|
enable, rc);
|
|
goto done;
|
|
}
|
|
|
|
if (ctrl->proc_clock_throttle && !ctrl->cpr_enabled) {
|
|
rc = cpr3_clock_enable(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
|
|
goto done;
|
|
}
|
|
ctrl->cpr_enabled = true;
|
|
|
|
cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
|
|
CPR3_PD_THROTTLE_DISABLE);
|
|
|
|
cpr3_clock_disable(ctrl);
|
|
ctrl->cpr_enabled = false;
|
|
}
|
|
|
|
cpr3_debug(ctrl, "closed-loop=%s\n", enable ? "enabled" : "disabled");
|
|
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_closed_loop_enable_get() - debugfs callback used to retrieve
|
|
* the value of the CPR controller cpr_allowed_sw flag which
|
|
* indicates if closed-loop operation is enabled
|
|
* @data: Pointer to private data which is equal to the CPR
|
|
* controller pointer
|
|
* @val: Output parameter written with the value of
|
|
* cpr_allowed_sw
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_closed_loop_enable_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
|
|
*val = ctrl->cpr_allowed_sw;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_closed_loop_enable_fops,
|
|
cpr3_debug_closed_loop_enable_get,
|
|
cpr3_debug_closed_loop_enable_set,
|
|
"%llu\n");
|
|
|
|
/**
|
|
* cpr3_debug_hw_closed_loop_enable_set() - debugfs callback used to change the
|
|
* value of the CPR controller use_hw_closed_loop flag which
|
|
* switches between software closed-loop and hardware closed-loop
|
|
* operation
|
|
* @data: Pointer to private data which is equal to the CPR
|
|
* controller pointer
|
|
* @val: New value for use_hw_closed_loop
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_hw_closed_loop_enable_set(void *data, u64 val)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
bool use_hw_closed_loop = !!val;
|
|
struct cpr3_regulator *vreg;
|
|
bool cpr_enabled;
|
|
int i, j, k, rc;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
if (ctrl->use_hw_closed_loop == use_hw_closed_loop)
|
|
goto done;
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
ctrl->use_hw_closed_loop = use_hw_closed_loop;
|
|
|
|
cpr_enabled = ctrl->cpr_enabled;
|
|
|
|
/* 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);
|
|
goto done;
|
|
}
|
|
ctrl->cpr_enabled = true;
|
|
}
|
|
|
|
if (ctrl->use_hw_closed_loop)
|
|
cpr3_write(ctrl, CPR3_REG_IRQ_EN, 0);
|
|
|
|
if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
|
|
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);
|
|
}
|
|
|
|
/* Turn off CPR clocks if they were off before this function call. */
|
|
if (!cpr_enabled) {
|
|
cpr3_clock_disable(ctrl);
|
|
ctrl->cpr_enabled = false;
|
|
}
|
|
|
|
if (ctrl->use_hw_closed_loop) {
|
|
rc = regulator_enable(ctrl->vdd_limit_regulator);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
|
|
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);
|
|
goto done;
|
|
}
|
|
} else {
|
|
rc = regulator_disable(ctrl->vdd_limit_regulator);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "CPR limit regulator disable failed, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
|
|
rc = msm_spm_avs_disable_irq(0, MSM_SPM_AVS_IRQ_MAX);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not disable max IRQ, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Due to APM and mem-acc floor restriction constraints, the closed-loop
|
|
* voltage may be different when using software closed-loop vs hardware
|
|
* closed-loop. Therefore, reset the cached closed-loop voltage for all
|
|
* corners to the corresponding open-loop voltage when switching between
|
|
* SW and HW closed-loop mode.
|
|
*/
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
vreg = &ctrl->thread[i].vreg[j];
|
|
for (k = 0; k < vreg->corner_count; k++)
|
|
vreg->corner[k].last_volt
|
|
= vreg->corner[k].open_loop_volt;
|
|
}
|
|
}
|
|
|
|
/* Skip last_volt caching */
|
|
ctrl->last_corner_was_closed_loop = false;
|
|
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not change CPR HW closed-loop enable state=%u, rc=%d\n",
|
|
use_hw_closed_loop, rc);
|
|
goto done;
|
|
}
|
|
|
|
cpr3_debug(ctrl, "closed-loop mode=%s\n",
|
|
use_hw_closed_loop ? "HW" : "SW");
|
|
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_debug_hw_closed_loop_enable_get() - debugfs callback used to retrieve
|
|
* the value of the CPR controller use_hw_closed_loop flag which
|
|
* indicates if hardware closed-loop operation is being used in
|
|
* place of software closed-loop operation
|
|
* @data: Pointer to private data which is equal to the CPR
|
|
* controller pointer
|
|
* @val: Output parameter written with the value of
|
|
* use_hw_closed_loop
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_hw_closed_loop_enable_get(void *data, u64 *val)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
|
|
*val = ctrl->use_hw_closed_loop;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_hw_closed_loop_enable_fops,
|
|
cpr3_debug_hw_closed_loop_enable_get,
|
|
cpr3_debug_hw_closed_loop_enable_set,
|
|
"%llu\n");
|
|
|
|
/**
|
|
* cpr3_debug_trigger_aging_measurement_set() - debugfs callback used to trigger
|
|
* another CPR measurement
|
|
* @data: Pointer to private data which is equal to the CPR
|
|
* controller pointer
|
|
* @val: Unused
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_debug_trigger_aging_measurement_set(void *data, u64 val)
|
|
{
|
|
struct cpr3_controller *ctrl = data;
|
|
int rc;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
|
|
ctrl->aging_required = true;
|
|
ctrl->aging_succeeded = false;
|
|
ctrl->aging_failed = false;
|
|
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not update the CPR controller state, rc=%d\n",
|
|
rc);
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_trigger_aging_measurement_fops,
|
|
NULL,
|
|
cpr3_debug_trigger_aging_measurement_set,
|
|
"%llu\n");
|
|
|
|
/**
|
|
* cpr3_regulator_debugfs_ctrl_add() - add debugfs files to expose configuration
|
|
* data for the CPR controller
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_debugfs_ctrl_add(struct cpr3_controller *ctrl)
|
|
{
|
|
struct dentry *temp, *aggr_dir;
|
|
int i;
|
|
|
|
/* Add cpr3-regulator base directory if it isn't present already. */
|
|
if (cpr3_debugfs_base == NULL) {
|
|
cpr3_debugfs_base = debugfs_create_dir("cpr3-regulator", NULL);
|
|
if (IS_ERR_OR_NULL(cpr3_debugfs_base)) {
|
|
cpr3_err(ctrl, "cpr3-regulator debugfs base directory creation failed\n");
|
|
cpr3_debugfs_base = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
ctrl->debugfs = debugfs_create_dir(ctrl->name, cpr3_debugfs_base);
|
|
if (IS_ERR_OR_NULL(ctrl->debugfs)) {
|
|
cpr3_err(ctrl, "cpr3-regulator controller debugfs directory creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("cpr_closed_loop_enable", S_IRUGO | S_IWUSR,
|
|
ctrl->debugfs, ctrl,
|
|
&cpr3_debug_closed_loop_enable_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "cpr_closed_loop_enable debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
if (ctrl->supports_hw_closed_loop) {
|
|
temp = debugfs_create_file("use_hw_closed_loop",
|
|
S_IRUGO | S_IWUSR, ctrl->debugfs, ctrl,
|
|
&cpr3_debug_hw_closed_loop_enable_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "use_hw_closed_loop debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
temp = debugfs_create_int("thread_count", S_IRUGO, ctrl->debugfs,
|
|
&ctrl->thread_count);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "thread_count debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
if (ctrl->apm) {
|
|
temp = debugfs_create_int("apm_threshold_volt", S_IRUGO,
|
|
ctrl->debugfs, &ctrl->apm_threshold_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "apm_threshold_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ctrl->aging_required || ctrl->aging_succeeded
|
|
|| ctrl->aging_failed) {
|
|
temp = debugfs_create_int("aging_adj_volt", S_IRUGO,
|
|
ctrl->debugfs, &ctrl->aging_ref_adjust_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aging_adj_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("aging_succeeded", S_IRUGO,
|
|
ctrl->debugfs, &ctrl->aging_succeeded, &fops_bool_ro);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aging_succeeded debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("aging_failed", S_IRUGO,
|
|
ctrl->debugfs, &ctrl->aging_failed, &fops_bool_ro);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aging_failed debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_file("aging_trigger", S_IWUSR,
|
|
ctrl->debugfs, ctrl,
|
|
&cpr3_debug_trigger_aging_measurement_fops);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aging_trigger debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
aggr_dir = debugfs_create_dir("max_aggregated_voltages", ctrl->debugfs);
|
|
if (IS_ERR_OR_NULL(aggr_dir)) {
|
|
cpr3_err(ctrl, "max_aggregated_voltages debugfs directory creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
|
|
&ctrl->aggr_corner.floor_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aggr floor_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
|
|
&ctrl->aggr_corner.ceiling_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aggr ceiling_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
|
|
&ctrl->aggr_corner.open_loop_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aggr open_loop_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
|
|
&ctrl->aggr_corner.last_volt);
|
|
if (IS_ERR_OR_NULL(temp)) {
|
|
cpr3_err(ctrl, "aggr last_volt debugfs file creation failed\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++)
|
|
cpr3_regulator_debugfs_thread_add(&ctrl->thread[i]);
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_debugfs_ctrl_remove() - remove debugfs files for the CPR
|
|
* controller
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Note, this function must be called after the controller has been removed from
|
|
* cpr3_controller_list and while the cpr3_controller_list_mutex lock is held.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void cpr3_regulator_debugfs_ctrl_remove(struct cpr3_controller *ctrl)
|
|
{
|
|
if (list_empty(&cpr3_controller_list)) {
|
|
debugfs_remove_recursive(cpr3_debugfs_base);
|
|
cpr3_debugfs_base = NULL;
|
|
} else {
|
|
debugfs_remove_recursive(ctrl->debugfs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_init_ctrl_data() - performs initialization of CPR controller
|
|
* elements
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_init_ctrl_data(struct cpr3_controller *ctrl)
|
|
{
|
|
/* Read the initial vdd voltage from hardware. */
|
|
ctrl->aggr_corner.last_volt
|
|
= regulator_get_voltage(ctrl->vdd_regulator);
|
|
if (ctrl->aggr_corner.last_volt < 0) {
|
|
cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
|
|
ctrl->aggr_corner.last_volt);
|
|
return ctrl->aggr_corner.last_volt;
|
|
}
|
|
ctrl->aggr_corner.open_loop_volt = ctrl->aggr_corner.last_volt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_init_vreg_data() - performs initialization of common CPR3
|
|
* regulator elements and validate aging configurations
|
|
* @vreg: Pointer to the CPR3 regulator
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_init_vreg_data(struct cpr3_regulator *vreg)
|
|
{
|
|
int i, j;
|
|
|
|
vreg->current_corner = CPR3_REGULATOR_CORNER_INVALID;
|
|
vreg->last_closed_loop_corner = CPR3_REGULATOR_CORNER_INVALID;
|
|
|
|
for (i = 0; i < vreg->corner_count; i++) {
|
|
vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
|
|
vreg->corner[i].irq_en = CPR3_IRQ_UP | CPR3_IRQ_DOWN;
|
|
|
|
vreg->corner[i].ro_mask = 0;
|
|
for (j = 0; j < CPR3_RO_COUNT; j++) {
|
|
if (vreg->corner[i].target_quot[j] == 0)
|
|
vreg->corner[i].ro_mask |= BIT(j);
|
|
}
|
|
}
|
|
|
|
if (vreg->aging_allowed && vreg->corner[vreg->aging_corner].ceiling_volt
|
|
> vreg->thread->ctrl->aging_ref_volt) {
|
|
cpr3_err(vreg, "aging corner %d ceiling voltage = %d > aging ref voltage = %d uV\n",
|
|
vreg->aging_corner,
|
|
vreg->corner[vreg->aging_corner].ceiling_volt,
|
|
vreg->thread->ctrl->aging_ref_volt);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_suspend() - perform common required CPR3 power down steps
|
|
* before the system enters suspend
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
|
|
rc = cpr3_closed_loop_disable(ctrl);
|
|
if (rc)
|
|
cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
|
|
|
|
ctrl->cpr_suspended = true;
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_resume() - perform common required CPR3 power up steps after
|
|
* the system resumes from suspend
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
int cpr3_regulator_resume(struct cpr3_controller *ctrl)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&ctrl->lock);
|
|
|
|
ctrl->cpr_suspended = false;
|
|
|
|
rc = cpr3_regulator_update_ctrl_state(ctrl);
|
|
if (rc)
|
|
cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
|
|
|
|
mutex_unlock(&ctrl->lock);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_validate_controller() - verify the data passed in via the
|
|
* cpr3_controller data structure
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
static int cpr3_regulator_validate_controller(struct cpr3_controller *ctrl)
|
|
{
|
|
int i;
|
|
|
|
if (!ctrl->vdd_regulator) {
|
|
cpr3_err(ctrl, "vdd regulator missing\n");
|
|
return -EINVAL;
|
|
} else if (ctrl->sensor_count <= 0
|
|
|| ctrl->sensor_count > CPR3_MAX_SENSOR_COUNT) {
|
|
cpr3_err(ctrl, "invalid CPR sensor count=%d\n",
|
|
ctrl->sensor_count);
|
|
return -EINVAL;
|
|
} else if (!ctrl->sensor_owner) {
|
|
cpr3_err(ctrl, "CPR sensor ownership table missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctrl->aging_required) {
|
|
for (i = 0; i < ctrl->aging_sensor_count; i++) {
|
|
if (ctrl->aging_sensor[i].sensor_id
|
|
>= ctrl->sensor_count) {
|
|
cpr3_err(ctrl, "aging_sensor[%d] id=%u is not in the value range 0-%d",
|
|
i, ctrl->aging_sensor[i].sensor_id,
|
|
ctrl->sensor_count - 1);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_register() - register the regulators for a CPR3 controller and
|
|
* perform CPR hardware initialization
|
|
* @pdev: Platform device pointer for the CPR3 controller
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
int cpr3_regulator_register(struct platform_device *pdev,
|
|
struct cpr3_controller *ctrl)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
int i, j, rc;
|
|
|
|
if (!dev->of_node) {
|
|
dev_err(dev, "%s: Device tree node is missing\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ctrl || !ctrl->name) {
|
|
dev_err(dev, "%s: CPR controller data is missing\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = cpr3_regulator_validate_controller(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "controller validation failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
mutex_init(&ctrl->lock);
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cpr_ctrl");
|
|
if (!res || !res->start) {
|
|
cpr3_err(ctrl, "CPR controller address is missing\n");
|
|
return -ENXIO;
|
|
}
|
|
ctrl->cpr_ctrl_base = devm_ioremap(dev, res->start, resource_size(res));
|
|
|
|
ctrl->irq = platform_get_irq_byname(pdev, "cpr");
|
|
if (ctrl->irq < 0) {
|
|
cpr3_err(ctrl, "missing CPR interrupt\n");
|
|
return ctrl->irq;
|
|
}
|
|
|
|
if (ctrl->supports_hw_closed_loop) {
|
|
rc = msm_spm_probe_done();
|
|
if (rc) {
|
|
if (rc != -EPROBE_DEFER)
|
|
cpr3_err(ctrl, "spm unavailable, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
ctrl->ceiling_irq = platform_get_irq_byname(pdev, "ceiling");
|
|
if (ctrl->ceiling_irq < 0) {
|
|
cpr3_err(ctrl, "missing ceiling interrupt\n");
|
|
return ctrl->ceiling_irq;
|
|
}
|
|
}
|
|
|
|
rc = cpr3_regulator_init_ctrl_data(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
rc = cpr3_regulator_init_vreg_data(
|
|
&ctrl->thread[i].vreg[j]);
|
|
if (rc)
|
|
return rc;
|
|
cpr3_print_quots(&ctrl->thread[i].vreg[j]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add the maximum possible aging voltage margin until it is possible
|
|
* to perform an aging measurement.
|
|
*/
|
|
if (ctrl->aging_required)
|
|
cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
|
|
|
|
rc = cpr3_regulator_init_ctrl(ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "CPR controller initialization failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Register regulator devices for all threads. */
|
|
for (i = 0; i < ctrl->thread_count; i++) {
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
|
|
rc = cpr3_regulator_vreg_register(
|
|
&ctrl->thread[i].vreg[j]);
|
|
if (rc) {
|
|
cpr3_err(&ctrl->thread[i].vreg[j], "failed to register regulator, rc=%d\n",
|
|
rc);
|
|
goto free_regulators;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = devm_request_threaded_irq(dev, ctrl->irq, NULL, cpr3_irq_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_RISING, "cpr3", ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not request IRQ %d, rc=%d\n",
|
|
ctrl->irq, rc);
|
|
goto free_regulators;
|
|
}
|
|
|
|
if (ctrl->supports_hw_closed_loop) {
|
|
rc = devm_request_threaded_irq(dev, ctrl->ceiling_irq, NULL,
|
|
cpr3_ceiling_irq_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
|
|
"cpr3_ceiling", ctrl);
|
|
if (rc) {
|
|
cpr3_err(ctrl, "could not request ceiling IRQ %d, rc=%d\n",
|
|
ctrl->ceiling_irq, rc);
|
|
goto free_regulators;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&cpr3_controller_list_mutex);
|
|
cpr3_regulator_debugfs_ctrl_add(ctrl);
|
|
list_add(&ctrl->list, &cpr3_controller_list);
|
|
mutex_unlock(&cpr3_controller_list_mutex);
|
|
|
|
return 0;
|
|
|
|
free_regulators:
|
|
for (i = 0; i < ctrl->thread_count; i++)
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++)
|
|
if (!IS_ERR_OR_NULL(ctrl->thread[i].vreg[j].rdev))
|
|
regulator_unregister(
|
|
ctrl->thread[i].vreg[j].rdev);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* cpr3_regulator_unregister() - unregister the regulators for a CPR3 controller
|
|
* and perform CPR hardware shutdown
|
|
* @ctrl: Pointer to the CPR3 controller
|
|
*
|
|
* Return: 0 on success, errno on failure
|
|
*/
|
|
int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
|
|
{
|
|
int i, j;
|
|
|
|
mutex_lock(&cpr3_controller_list_mutex);
|
|
list_del(&ctrl->list);
|
|
cpr3_regulator_debugfs_ctrl_remove(ctrl);
|
|
mutex_unlock(&cpr3_controller_list_mutex);
|
|
|
|
cpr3_ctrl_loop_disable(ctrl);
|
|
cpr3_closed_loop_disable(ctrl);
|
|
|
|
if (ctrl->use_hw_closed_loop) {
|
|
regulator_disable(ctrl->vdd_limit_regulator);
|
|
msm_spm_avs_disable_irq(0, MSM_SPM_AVS_IRQ_MAX);
|
|
}
|
|
|
|
for (i = 0; i < ctrl->thread_count; i++)
|
|
for (j = 0; j < ctrl->thread[i].vreg_count; j++)
|
|
regulator_unregister(ctrl->thread[i].vreg[j].rdev);
|
|
|
|
return 0;
|
|
}
|