mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			12147 lines
		
	
	
	
		
			368 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			12147 lines
		
	
	
	
		
			368 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 303fb163bb86f04432c93325ff8b9638c9e50641 Mon Sep 17 00:00:00 2001
 | |
| From: Robert Marko <robimarko@gmail.com>
 | |
| Date: Mon, 11 Apr 2022 14:35:36 +0200
 | |
| Subject: [PATCH] regulator: add Qualcomm CPR regulators
 | |
| 
 | |
| Add Qualcomm CPR driver, which allows using the CPR HW to calculate the
 | |
| correct OPP point voltage dynamically based on the system load.
 | |
| 
 | |
| Signed-off-by: Robert Marko <robimarko@gmail.com>
 | |
| ---
 | |
|  drivers/regulator/Kconfig               |   33 +
 | |
|  drivers/regulator/Makefile              |    3 +
 | |
|  drivers/regulator/cpr3-npu-regulator.c  |  695 +++
 | |
|  drivers/regulator/cpr3-regulator.c      | 5112 +++++++++++++++++++++++
 | |
|  drivers/regulator/cpr3-regulator.h      | 1211 ++++++
 | |
|  drivers/regulator/cpr3-util.c           | 2750 ++++++++++++
 | |
|  drivers/regulator/cpr4-apss-regulator.c | 1819 ++++++++
 | |
|  include/soc/qcom/socinfo.h              |  463 ++
 | |
|  8 files changed, 12086 insertions(+)
 | |
|  create mode 100644 drivers/regulator/cpr3-npu-regulator.c
 | |
|  create mode 100644 drivers/regulator/cpr3-regulator.c
 | |
|  create mode 100644 drivers/regulator/cpr3-regulator.h
 | |
|  create mode 100644 drivers/regulator/cpr3-util.c
 | |
|  create mode 100644 drivers/regulator/cpr4-apss-regulator.c
 | |
|  create mode 100644 include/soc/qcom/socinfo.h
 | |
| 
 | |
| --- a/drivers/regulator/Kconfig
 | |
| +++ b/drivers/regulator/Kconfig
 | |
| @@ -1423,5 +1423,38 @@ config REGULATOR_QCOM_LABIBB
 | |
|  	  boost regulator and IBB can be used as a negative boost regulator
 | |
|  	  for LCD display panel.
 | |
|  
 | |
| +config REGULATOR_CPR3
 | |
| +	bool "QCOM CPR3 regulator core support"
 | |
| +	help
 | |
| +	  This driver supports Core Power Reduction (CPR) version 3 controllers
 | |
| +	  which are used by some Qualcomm Technologies, Inc. SoCs to
 | |
| +	  manage important voltage regulators.  CPR3 controllers are capable of
 | |
| +	  monitoring several ring oscillator sensing loops simultaneously.  The
 | |
| +	  CPR3 controller informs software when the silicon conditions require
 | |
| +	  the supply voltage to be increased or decreased.  On certain supply
 | |
| +	  rails, the CPR3 controller is able to propagate the voltage increase
 | |
| +	  or decrease requests all the way to the PMIC without software
 | |
| +	  involvement.
 | |
| +
 | |
| +config REGULATOR_CPR3_NPU
 | |
| +	bool "QCOM CPR3 regulator for NPU"
 | |
| +	depends on OF && REGULATOR_CPR3
 | |
| +	help
 | |
| +	  This driver supports Qualcomm Technologies, Inc. NPU CPR3
 | |
| +	  regulator Which will always operate in open loop.
 | |
| +
 | |
| +config REGULATOR_CPR4_APSS
 | |
| +	bool "QCOM CPR4 regulator for APSS"
 | |
| +	depends on OF && REGULATOR_CPR3
 | |
| +	help
 | |
| +	  This driver supports Qualcomm Technologies, Inc. APSS application
 | |
| +	  processor specific features including memory array power mux (APM)
 | |
| +	  switching, one CPR4 thread which monitor the two APSS clusters that
 | |
| +	  are both powered by a shared supply, hardware closed-loop auto
 | |
| +	  voltage stepping, voltage adjustments based on online core count,
 | |
| +	  voltage adjustments based on temperature readings, and voltage
 | |
| +	  adjustments for performance boost mode. This driver reads both initial
 | |
| +	  voltage and CPR target quotient values out of hardware fuses.
 | |
| +
 | |
|  endif
 | |
|  
 | |
| --- a/drivers/regulator/Makefile
 | |
| +++ b/drivers/regulator/Makefile
 | |
| @@ -105,6 +105,9 @@ obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qco
 | |
|  obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
 | |
|  obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
 | |
|  obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o
 | |
| +obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o
 | |
| +obj-$(CONFIG_REGULATOR_CPR3_NPU) += cpr3-npu-regulator.o
 | |
| +obj-$(CONFIG_REGULATOR_CPR4_APSS) += cpr4-apss-regulator.o
 | |
|  obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
 | |
|  obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o
 | |
|  obj-$(CONFIG_REGULATOR_PF8X00) += pf8x00-regulator.o
 | |
| --- /dev/null
 | |
| +++ b/drivers/regulator/cpr3-npu-regulator.c
 | |
| @@ -0,0 +1,695 @@
 | |
| +/*
 | |
| + * Copyright (c) 2017, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * Permission to use, copy, modify, and/or distribute this software for any
 | |
| + * purpose with or without fee is hereby granted, provided that the above
 | |
| + * copyright notice and this permission notice appear in all copies.
 | |
| + *
 | |
| + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | |
| + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | |
| + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | |
| + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | |
| + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 | |
| + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | |
| + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | |
| + */
 | |
| +
 | |
| +#include <linux/err.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/of_device.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/thermal.h>
 | |
| +
 | |
| +#include "cpr3-regulator.h"
 | |
| +
 | |
| +#define IPQ807x_NPU_FUSE_CORNERS		2
 | |
| +#define IPQ817x_NPU_FUSE_CORNERS		1
 | |
| +#define IPQ807x_NPU_FUSE_STEP_VOLT		8000
 | |
| +#define IPQ807x_NPU_VOLTAGE_FUSE_SIZE		6
 | |
| +#define IPQ807x_NPU_CPR_CLOCK_RATE		19200000
 | |
| +
 | |
| +#define IPQ807x_NPU_CPR_TCSR_START		6
 | |
| +#define IPQ807x_NPU_CPR_TCSR_END		7
 | |
| +
 | |
| +#define NPU_TSENS				5
 | |
| +
 | |
| +u32 g_valid_npu_fuse_count = IPQ807x_NPU_FUSE_CORNERS;
 | |
| +/**
 | |
| + * struct cpr3_ipq807x_npu_fuses - NPU specific fuse data for IPQ807x
 | |
| + * @init_voltage:	Initial (i.e. open-loop) voltage fuse parameter value
 | |
| + *			for each fuse corner (raw, not converted to a voltage)
 | |
| + * This struct holds the values for all of the fuses read from memory.
 | |
| + */
 | |
| +struct cpr3_ipq807x_npu_fuses {
 | |
| +	u64	init_voltage[IPQ807x_NPU_FUSE_CORNERS];
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * Constants which define the name of each fuse corner.
 | |
| + */
 | |
| +enum cpr3_ipq807x_npu_fuse_corner {
 | |
| +	CPR3_IPQ807x_NPU_FUSE_CORNER_NOM	= 0,
 | |
| +	CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO	= 1,
 | |
| +};
 | |
| +
 | |
| +static const char * const cpr3_ipq807x_npu_fuse_corner_name[] = {
 | |
| +	[CPR3_IPQ807x_NPU_FUSE_CORNER_NOM]	= "NOM",
 | |
| +	[CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO]	= "TURBO",
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * IPQ807x NPU fuse parameter locations:
 | |
| + *
 | |
| + * Structs are organized with the following dimensions:
 | |
| + *	Outer: 0 to 1 for fuse corners from lowest to highest corner
 | |
| + *	Inner: large enough to hold the longest set of parameter segments which
 | |
| + *		fully defines a fuse parameter, +1 (for NULL termination).
 | |
| + *		Each segment corresponds to a contiguous group of bits from a
 | |
| + *		single fuse row.  These segments are concatentated together in
 | |
| + *		order to form the full fuse parameter value.  The segments for
 | |
| + *		a given parameter may correspond to different fuse rows.
 | |
| + */
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq807x_npu_init_voltage_param[IPQ807x_NPU_FUSE_CORNERS][2] = {
 | |
| +	{{73, 22, 27}, {} },
 | |
| +	{{73, 16, 21}, {} },
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
 | |
| + */
 | |
| +static int
 | |
| +ipq807x_npu_fuse_ref_volt [IPQ807x_NPU_FUSE_CORNERS] = {
 | |
| +	912000,
 | |
| +	992000,
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * IPQ9574 (Few parameters are changed, remaining are same as IPQ807x)
 | |
| + */
 | |
| +#define IPQ9574_NPU_FUSE_CORNERS		2
 | |
| +#define IPQ9574_NPU_FUSE_STEP_VOLT		10000
 | |
| +#define IPQ9574_NPU_CPR_CLOCK_RATE		24000000
 | |
| +
 | |
| +/*
 | |
| + * fues parameters for IPQ9574
 | |
| + */
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq9574_npu_init_voltage_param[IPQ9574_NPU_FUSE_CORNERS][2] = {
 | |
| +	{{105, 12, 17}, {} },
 | |
| +	{{105,  6, 11}, {} },
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * Open loop voltage fuse reference voltages in microvolts for IPQ9574
 | |
| + */
 | |
| +static int
 | |
| +ipq9574_npu_fuse_ref_volt [IPQ9574_NPU_FUSE_CORNERS] = {
 | |
| +	862500,
 | |
| +	987500,
 | |
| +};
 | |
| +
 | |
| +struct cpr3_controller *g_ctrl;
 | |
| +
 | |
| +void cpr3_npu_temp_notify(int sensor, int temp, int low_notif)
 | |
| +{
 | |
| +	u32 prev_sensor_state;
 | |
| +
 | |
| +	if (sensor != NPU_TSENS)
 | |
| +		return;
 | |
| +
 | |
| +	prev_sensor_state = g_ctrl->cur_sensor_state;
 | |
| +	if (low_notif)
 | |
| +		g_ctrl->cur_sensor_state |= BIT(sensor);
 | |
| +	else
 | |
| +		g_ctrl->cur_sensor_state &= ~BIT(sensor);
 | |
| +
 | |
| +	if (!prev_sensor_state && g_ctrl->cur_sensor_state)
 | |
| +		cpr3_handle_temp_open_loop_adjustment(g_ctrl, true);
 | |
| +	else if (prev_sensor_state && !g_ctrl->cur_sensor_state)
 | |
| +		cpr3_handle_temp_open_loop_adjustment(g_ctrl, false);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_ipq807x_npu_read_fuse_data() - load NPU specific fuse parameter values
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function allocates a cpr3_ipq807x_npu_fuses struct, fills it with
 | |
| + * values read out of hardware fuses, and finally copies common fuse values
 | |
| + * into the CPR3 regulator struct.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_ipq807x_npu_read_fuse_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	void __iomem *base = vreg->thread->ctrl->fuse_base;
 | |
| +	struct cpr3_ipq807x_npu_fuses *fuse;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
 | |
| +	if (!fuse)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	for (i = 0; i < g_valid_npu_fuse_count; i++) {
 | |
| +		rc = cpr3_read_fuse_param(base,
 | |
| +					  vreg->cpr3_regulator_data->init_voltage_param[i],
 | |
| +					  &fuse->init_voltage[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
 | |
| +				 i, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	vreg->fuse_corner_count	= g_valid_npu_fuse_count;
 | |
| +	vreg->platform_fuses	= fuse;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_npu_parse_corner_data() - parse NPU corner data from device tree
 | |
| + *		properties of the CPR3 regulator's device node
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_npu_parse_corner_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_common_corner_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_ipq807x_npu_calculate_open_loop_voltages() - calculate the open-loop
 | |
| + *		voltage for each corner of a CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @temp_correction:    Temperature based correction
 | |
| + *
 | |
| + * If open-loop voltage interpolation is allowed in device tree, then
 | |
| + * this function calculates the open-loop voltage for a given corner using
 | |
| + * linear interpolation.  This interpolation is performed using the processor
 | |
| + * frequencies of the lower and higher Fmax corners along with their fused
 | |
| + * open-loop voltages.
 | |
| + *
 | |
| + * If open-loop voltage interpolation is not allowed, then this function uses
 | |
| + * the Fmax fused open-loop voltage for all of the corners associated with a
 | |
| + * given fuse corner.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_ipq807x_npu_calculate_open_loop_voltages(
 | |
| +			struct cpr3_regulator *vreg, bool temp_correction)
 | |
| +{
 | |
| +	struct cpr3_ipq807x_npu_fuses *fuse = vreg->platform_fuses;
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	int i, j, rc = 0;
 | |
| +	u64 freq_low, volt_low, freq_high, volt_high;
 | |
| +	int *fuse_volt;
 | |
| +	int *fmax_corner;
 | |
| +
 | |
| +	fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
 | |
| +			    GFP_KERNEL);
 | |
| +	fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
 | |
| +			      GFP_KERNEL);
 | |
| +	if (!fuse_volt || !fmax_corner) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (ctrl->cpr_global_setting == CPR_DISABLED)
 | |
| +			fuse_volt[i] = vreg->cpr3_regulator_data->fuse_ref_volt[i];
 | |
| +		else
 | |
| +			fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
 | |
| +				vreg->cpr3_regulator_data->fuse_ref_volt[i],
 | |
| +				vreg->cpr3_regulator_data->fuse_step_volt,
 | |
| +				fuse->init_voltage[i],
 | |
| +				IPQ807x_NPU_VOLTAGE_FUSE_SIZE);
 | |
| +
 | |
| +		/* Log fused open-loop voltage values for debugging purposes. */
 | |
| +		cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
 | |
| +			  cpr3_ipq807x_npu_fuse_corner_name[i],
 | |
| +			  fuse_volt[i]);
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_determine_part_type(vreg,
 | |
| +			fuse_volt[CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO]);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg,
 | |
| +			"fused part type detection failed failed, rc=%d\n", rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg,
 | |
| +			"fused open-loop voltage adjustment failed, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +	if (temp_correction) {
 | |
| +		rc = cpr3_determine_temp_base_open_loop_correction(vreg,
 | |
| +								fuse_volt);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg,
 | |
| +				"temp open-loop voltage adj. failed, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (fuse_volt[i] < fuse_volt[i - 1]) {
 | |
| +			cpr3_info(vreg,
 | |
| +				"fuse corner %d voltage=%d uV < fuse corner %d \
 | |
| +				voltage=%d uV; overriding: fuse corner %d \
 | |
| +				voltage=%d\n",
 | |
| +				  i, fuse_volt[i], i - 1, fuse_volt[i - 1],
 | |
| +				  i, fuse_volt[i - 1]);
 | |
| +			fuse_volt[i] = fuse_volt[i - 1];
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Determine highest corner mapped to each fuse corner */
 | |
| +	j = vreg->fuse_corner_count - 1;
 | |
| +	for (i = vreg->corner_count - 1; i >= 0; i--) {
 | |
| +		if (vreg->corner[i].cpr_fuse_corner == j) {
 | |
| +			fmax_corner[j] = i;
 | |
| +			j--;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (j >= 0) {
 | |
| +		cpr3_err(vreg, "invalid fuse corner mapping\n");
 | |
| +		rc = -EINVAL;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Interpolation is not possible for corners mapped to the lowest fuse
 | |
| +	 * corner so use the fuse corner value directly.
 | |
| +	 */
 | |
| +	for (i = 0; i <= fmax_corner[0]; i++)
 | |
| +		vreg->corner[i].open_loop_volt = fuse_volt[0];
 | |
| +
 | |
| +	/* Interpolate voltages for the higher fuse corners. */
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
 | |
| +		volt_low = fuse_volt[i - 1];
 | |
| +		freq_high = vreg->corner[fmax_corner[i]].proc_freq;
 | |
| +		volt_high = fuse_volt[i];
 | |
| +
 | |
| +		for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
 | |
| +			vreg->corner[j].open_loop_volt = cpr3_interpolate(
 | |
| +				freq_low, volt_low, freq_high, volt_high,
 | |
| +				vreg->corner[j].proc_freq);
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	if (rc == 0) {
 | |
| +		cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
 | |
| +				   vreg->corner[i].open_loop_volt);
 | |
| +
 | |
| +		rc = cpr3_adjust_open_loop_voltages(vreg);
 | |
| +		if (rc)
 | |
| +			cpr3_err(vreg,
 | |
| +				"open-loop voltage adjustment failed, rc=%d\n",
 | |
| +				 rc);
 | |
| +	}
 | |
| +
 | |
| +	kfree(fuse_volt);
 | |
| +	kfree(fmax_corner);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_npu_print_settings() - print out NPU CPR configuration settings into
 | |
| + *		the kernel log for debugging purposes
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + */
 | |
| +static void cpr3_npu_print_settings(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_corner *corner;
 | |
| +	int i;
 | |
| +
 | |
| +	cpr3_debug(vreg,
 | |
| +		"Corner: Frequency (Hz), Fuse Corner, Floor (uV), \
 | |
| +		Open-Loop (uV), Ceiling (uV)\n");
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		corner = &vreg->corner[i];
 | |
| +		cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
 | |
| +			   i, corner->proc_freq, corner->cpr_fuse_corner,
 | |
| +			   corner->floor_volt, corner->open_loop_volt,
 | |
| +			   corner->ceiling_volt);
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->thread->ctrl->apm)
 | |
| +		cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
 | |
| +			   vreg->thread->ctrl->apm_threshold_volt,
 | |
| +			   vreg->thread->ctrl->apm_adj_volt);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_ipq807x_npu_calc_temp_based_ol_voltages() - Calculate the open loop
 | |
| + * voltages based on temperature based correction margins
 | |
| + * @vreg:               Pointer to the CPR3 regulator
 | |
| + */
 | |
| +
 | |
| +static int
 | |
| +cpr3_ipq807x_npu_calc_temp_based_ol_voltages(struct cpr3_regulator *vreg,
 | |
| +						bool temp_correction)
 | |
| +{
 | |
| +	int rc, i;
 | |
| +
 | |
| +	rc = cpr3_ipq807x_npu_calculate_open_loop_voltages(vreg,
 | |
| +							temp_correction);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg,
 | |
| +			"unable to calculate open-loop voltages, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_limit_open_loop_voltages(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	cpr3_open_loop_voltage_as_ceiling(vreg);
 | |
| +
 | |
| +	rc = cpr3_limit_floor_voltages(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		if (temp_correction)
 | |
| +			vreg->corner[i].cold_temp_open_loop_volt =
 | |
| +				vreg->corner[i].open_loop_volt;
 | |
| +		else
 | |
| +			vreg->corner[i].normal_temp_open_loop_volt =
 | |
| +				vreg->corner[i].open_loop_volt;
 | |
| +	}
 | |
| +
 | |
| +	cpr3_npu_print_settings(vreg);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_npu_init_thread() - perform steps necessary to initialize the
 | |
| + *		configuration data for a CPR3 thread
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_npu_init_thread(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_common_thread_data(thread);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(thread->ctrl,
 | |
| +			"thread %u CPR thread data from DT- failed, rc=%d\n",
 | |
| +			 thread->thread_id, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_npu_init_regulator() - perform all steps necessary to initialize the
 | |
| + *		configuration data for a CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_npu_init_regulator(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_ipq807x_npu_fuses *fuse;
 | |
| +	int rc, cold_temp = 0;
 | |
| +	bool can_adj_cold_temp = cpr3_can_adjust_cold_temp(vreg);
 | |
| +
 | |
| +	rc = cpr3_ipq807x_npu_read_fuse_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	fuse = vreg->platform_fuses;
 | |
| +
 | |
| +	rc = cpr3_npu_parse_corner_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg,
 | |
| +			"Cannot read CPR corner data from DT, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_mem_acc_init(vreg);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(vreg,
 | |
| +			"Cannot initialize mem-acc regulator settings, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (can_adj_cold_temp) {
 | |
| +		rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, true);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg,
 | |
| +			"unable to calculate open-loop voltages, rc=%d\n", rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, false);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg,
 | |
| +			"unable to calculate open-loop voltages, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (can_adj_cold_temp) {
 | |
| +		cpr3_info(vreg,
 | |
| +		"Normal and Cold condition init done. Default to normal.\n");
 | |
| +
 | |
| +		rc = cpr3_get_cold_temp_threshold(vreg, &cold_temp);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg,
 | |
| +			"Get cold temperature threshold failed, rc=%d\n", rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +		register_low_temp_notif(NPU_TSENS, cold_temp,
 | |
| +							cpr3_npu_temp_notify);
 | |
| +	}
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_npu_init_controller() - perform NPU CPR3 controller specific
 | |
| + *		initializations
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_npu_init_controller(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_open_loop_common_ctrl_data(ctrl);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
 | |
| +				 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->ctrl_type = CPR_CTRL_TYPE_CPR3;
 | |
| +	ctrl->supports_hw_closed_loop = false;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static const struct cpr3_reg_data ipq807x_cpr_npu = {
 | |
| +	.cpr_valid_fuse_count = IPQ807x_NPU_FUSE_CORNERS,
 | |
| +	.init_voltage_param = ipq807x_npu_init_voltage_param,
 | |
| +	.fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
 | |
| +};
 | |
| +
 | |
| +static const struct cpr3_reg_data ipq817x_cpr_npu = {
 | |
| +	.cpr_valid_fuse_count = IPQ817x_NPU_FUSE_CORNERS,
 | |
| +	.init_voltage_param = ipq807x_npu_init_voltage_param,
 | |
| +	.fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
 | |
| +};
 | |
| +
 | |
| +static const struct cpr3_reg_data ipq9574_cpr_npu = {
 | |
| +	.cpr_valid_fuse_count = IPQ9574_NPU_FUSE_CORNERS,
 | |
| +	.init_voltage_param = ipq9574_npu_init_voltage_param,
 | |
| +	.fuse_ref_volt = ipq9574_npu_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ9574_NPU_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ9574_NPU_CPR_CLOCK_RATE,
 | |
| +};
 | |
| +
 | |
| +static struct of_device_id cpr3_regulator_match_table[] = {
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr3-ipq807x-npu-regulator",
 | |
| +		.data = &ipq807x_cpr_npu
 | |
| +	},
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr3-ipq817x-npu-regulator",
 | |
| +		.data = &ipq817x_cpr_npu
 | |
| +	},
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr3-ipq9574-npu-regulator",
 | |
| +		.data = &ipq9574_cpr_npu
 | |
| +	},
 | |
| +	{}
 | |
| +};
 | |
| +
 | |
| +static int cpr3_npu_regulator_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct cpr3_controller *ctrl;
 | |
| +	int i, rc;
 | |
| +	const struct of_device_id *match;
 | |
| +	struct cpr3_reg_data *cpr_data;
 | |
| +
 | |
| +	if (!dev->of_node) {
 | |
| +		dev_err(dev, "Device tree node is missing\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
 | |
| +	if (!ctrl)
 | |
| +		return -ENOMEM;
 | |
| +	g_ctrl = ctrl;
 | |
| +
 | |
| +	match = of_match_device(cpr3_regulator_match_table, &pdev->dev);
 | |
| +	if (!match)
 | |
| +		return -ENODEV;
 | |
| +
 | |
| +	cpr_data = (struct cpr3_reg_data *)match->data;
 | |
| +	g_valid_npu_fuse_count = cpr_data->cpr_valid_fuse_count;
 | |
| +	dev_info(dev, "NPU CPR valid fuse count: %d\n", g_valid_npu_fuse_count);
 | |
| +	ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
 | |
| +
 | |
| +	ctrl->dev = dev;
 | |
| +	/* Set to false later if anything precludes CPR operation. */
 | |
| +	ctrl->cpr_allowed_hw = true;
 | |
| +
 | |
| +	rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
 | |
| +				     &ctrl->name);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_map_fuse_base(ctrl, pdev);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "could not map fuse base address\n");
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_NPU_CPR_TCSR_START,
 | |
| +				    IPQ807x_NPU_CPR_TCSR_END);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "could not read CPR tcsr rsetting\n");
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_allocate_threads(ctrl, 0, 0);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->thread_count != 1) {
 | |
| +		cpr3_err(ctrl, "expected 1 thread but found %d\n",
 | |
| +			 ctrl->thread_count);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_npu_init_controller(ctrl);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
 | |
| +				 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_npu_init_thread(&ctrl->thread[0]);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
 | |
| +		ctrl->thread[0].vreg[i].cpr3_regulator_data = cpr_data;
 | |
| +		rc = cpr3_npu_init_regulator(&ctrl->thread[0].vreg[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
 | |
| +				 rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	platform_set_drvdata(pdev, ctrl);
 | |
| +
 | |
| +	return cpr3_open_loop_regulator_register(pdev, ctrl);
 | |
| +}
 | |
| +
 | |
| +static int cpr3_npu_regulator_remove(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
 | |
| +
 | |
| +	return cpr3_open_loop_regulator_unregister(ctrl);
 | |
| +}
 | |
| +
 | |
| +static struct platform_driver cpr3_npu_regulator_driver = {
 | |
| +	.driver		= {
 | |
| +		.name		= "qcom,cpr3-npu-regulator",
 | |
| +		.of_match_table	= cpr3_regulator_match_table,
 | |
| +		.owner		= THIS_MODULE,
 | |
| +	},
 | |
| +	.probe		= cpr3_npu_regulator_probe,
 | |
| +	.remove		= cpr3_npu_regulator_remove,
 | |
| +};
 | |
| +
 | |
| +static int cpr3_regulator_init(void)
 | |
| +{
 | |
| +	return platform_driver_register(&cpr3_npu_regulator_driver);
 | |
| +}
 | |
| +arch_initcall(cpr3_regulator_init);
 | |
| +
 | |
| +static void cpr3_regulator_exit(void)
 | |
| +{
 | |
| +	platform_driver_unregister(&cpr3_npu_regulator_driver);
 | |
| +}
 | |
| +module_exit(cpr3_regulator_exit);
 | |
| +
 | |
| +MODULE_DESCRIPTION("QCOM CPR3 NPU regulator driver");
 | |
| +MODULE_LICENSE("Dual BSD/GPLv2");
 | |
| +MODULE_ALIAS("platform:npu-ipq807x");
 | |
| --- /dev/null
 | |
| +++ b/drivers/regulator/cpr3-regulator.c
 | |
| @@ -0,0 +1,5112 @@
 | |
| +/*
 | |
| + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 and
 | |
| + * only version 2 as published by the Free Software Foundation.
 | |
| + *
 | |
| + * This program is distributed in the hope that it will be useful,
 | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| + * GNU General Public License for more details.
 | |
| + */
 | |
| +
 | |
| +#define pr_fmt(fmt) "%s: " fmt, __func__
 | |
| +
 | |
| +#include <linux/bitops.h>
 | |
| +#include <linux/debugfs.h>
 | |
| +#include <linux/delay.h>
 | |
| +#include <linux/err.h>
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/interrupt.h>
 | |
| +#include <linux/io.h>
 | |
| +#include <linux/kernel.h>
 | |
| +#include <linux/ktime.h>
 | |
| +#include <linux/list.h>
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/of_device.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/pm_opp.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/sort.h>
 | |
| +#include <linux/string.h>
 | |
| +#include <linux/uaccess.h>
 | |
| +#include <linux/regulator/driver.h>
 | |
| +#include <linux/regulator/machine.h>
 | |
| +#include <linux/regulator/of_regulator.h>
 | |
| +#include <linux/panic_notifier.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_CPR_TIMER_CLAMP			0x10
 | |
| +#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN	BIT(27)
 | |
| +
 | |
| +#define CPR4_REG_MISC				0x700
 | |
| +#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK	GENMASK(23, 20)
 | |
| +#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT	20
 | |
| +#define CPR4_MISC_TEMP_SENSOR_ID_START_MASK	GENMASK(27, 24)
 | |
| +#define CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT	24
 | |
| +#define CPR4_MISC_TEMP_SENSOR_ID_END_MASK	GENMASK(31, 28)
 | |
| +#define CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT	28
 | |
| +
 | |
| +#define CPR4_REG_SAW_ERROR_STEP_LIMIT		0x7A4
 | |
| +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK	GENMASK(4, 0)
 | |
| +#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT	0
 | |
| +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK	GENMASK(9, 5)
 | |
| +#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT	5
 | |
| +
 | |
| +#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS			0x7A8
 | |
| +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK	GENMASK(28, 18)
 | |
| +#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT	18
 | |
| +
 | |
| +#define CPR4_REG_MARGIN_TEMP_CORE(core)		(0x7AC + 0x4 * (core))
 | |
| +#define CPR4_MARGIN_TEMP_CORE_ADJ_MASK		GENMASK(7, 0)
 | |
| +#define CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT		8
 | |
| +
 | |
| +#define CPR4_REG_MARGIN_TEMP_POINT0N1		0x7F0
 | |
| +#define CPR4_MARGIN_TEMP_POINT0_MASK		GENMASK(11, 0)
 | |
| +#define CPR4_MARGIN_TEMP_POINT0_SHIFT		0
 | |
| +#define CPR4_MARGIN_TEMP_POINT1_MASK		GENMASK(23, 12)
 | |
| +#define CPR4_MARGIN_TEMP_POINT1_SHIFT		12
 | |
| +#define CPR4_REG_MARGIN_TEMP_POINT2		0x7F4
 | |
| +#define CPR4_MARGIN_TEMP_POINT2_MASK		GENMASK(11, 0)
 | |
| +#define CPR4_MARGIN_TEMP_POINT2_SHIFT		0
 | |
| +
 | |
| +#define CPR4_REG_MARGIN_ADJ_CTL					0x7F8
 | |
| +#define CPR4_MARGIN_ADJ_CTL_BOOST_EN				BIT(0)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN				BIT(1)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN				BIT(2)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN		BIT(3)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK		BIT(4)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE		BIT(4)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE		0
 | |
| +#define CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN			BIT(7)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN			BIT(8)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK			GENMASK(16, 12)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT		12
 | |
| +#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK		GENMASK(21, 19)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT		19
 | |
| +#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK			GENMASK(25, 22)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT			22
 | |
| +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK	GENMASK(31, 26)
 | |
| +#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT	26
 | |
| +
 | |
| +#define CPR4_REG_CPR_MASK_THREAD(thread)	(0x80C + 0x440 * (thread))
 | |
| +#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD		BIT(31)
 | |
| +#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK	GENMASK(15, 0)
 | |
| +
 | |
| +/*
 | |
| + * 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 && !(ctrl->aggr_corner.sdelta
 | |
| +		&& ctrl->aggr_corner.sdelta->allow_boost))
 | |
| +		cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
 | |
| +			CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_ENABLE);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_ctrl_loop_disable() - disable the CPR sensing loop for a given
 | |
| + *		controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +static inline void cpr3_ctrl_loop_disable(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	if (ctrl->cpr_enabled)
 | |
| +		cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
 | |
| +			CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_DISABLE);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_clock_enable() - prepare and enable all clocks used by this CPR3
 | |
| + *		controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_clock_enable(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = clk_prepare_enable(ctrl->bus_clk);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "failed to enable bus clock, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = clk_prepare_enable(ctrl->iface_clk);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "failed to enable interface clock, rc=%d\n", rc);
 | |
| +		clk_disable_unprepare(ctrl->bus_clk);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = clk_prepare_enable(ctrl->core_clk);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "failed to enable core clock, rc=%d\n", rc);
 | |
| +		clk_disable_unprepare(ctrl->iface_clk);
 | |
| +		clk_disable_unprepare(ctrl->bus_clk);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_clock_disable() - disable and unprepare all clocks used by this CPR3
 | |
| + *		controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +static void cpr3_clock_disable(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	clk_disable_unprepare(ctrl->core_clk);
 | |
| +	clk_disable_unprepare(ctrl->iface_clk);
 | |
| +	clk_disable_unprepare(ctrl->bus_clk);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_ctrl_clear_cpr4_config() - clear the CPR4 register configuration
 | |
| + *		programmed for current aggregated corner of a given controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static inline int cpr3_ctrl_clear_cpr4_config(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct cpr4_sdelta *aggr_sdelta = ctrl->aggr_corner.sdelta;
 | |
| +	bool cpr_enabled = ctrl->cpr_enabled;
 | |
| +	int i, rc = 0;
 | |
| +
 | |
| +	if (!aggr_sdelta || !(aggr_sdelta->allow_core_count_adj
 | |
| +		|| aggr_sdelta->allow_temp_adj || aggr_sdelta->allow_boost))
 | |
| +		/* cpr4 features are not enabled */
 | |
| +		return 0;
 | |
| +
 | |
| +	/* Ensure that CPR clocks are enabled before writing to registers. */
 | |
| +	if (!cpr_enabled) {
 | |
| +		rc = cpr3_clock_enable(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +		ctrl->cpr_enabled = true;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Clear feature enable configuration made for current
 | |
| +	 * aggregated corner.
 | |
| +	 */
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +		CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
 | |
| +		| CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_BOOST_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK, 0);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MISC,
 | |
| +			CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
 | |
| +			0 << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
 | |
| +
 | |
| +	for (i = 0; i <= aggr_sdelta->max_core_count; i++) {
 | |
| +		/* Clear voltage margin adjustments programmed in TEMP_COREi */
 | |
| +		cpr3_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE(i), 0);
 | |
| +	}
 | |
| +
 | |
| +	/* Turn off CPR clocks if they were off before this function call. */
 | |
| +	if (!cpr_enabled) {
 | |
| +		cpr3_clock_disable(ctrl);
 | |
| +		ctrl->cpr_enabled = false;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_closed_loop_enable() - enable logical CPR closed-loop operation
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_closed_loop_enable(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	if (!ctrl->cpr_allowed_hw || !ctrl->cpr_allowed_sw) {
 | |
| +		cpr3_err(ctrl, "cannot enable closed-loop CPR operation because it is disallowed\n");
 | |
| +		return -EPERM;
 | |
| +	} else if (ctrl->cpr_enabled) {
 | |
| +		/* Already enabled */
 | |
| +		return 0;
 | |
| +	} else if (ctrl->cpr_suspended) {
 | |
| +		/*
 | |
| +		 * CPR must remain disabled as the system is entering suspend.
 | |
| +		 */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_clock_enable(ctrl);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "unable to enable CPR clocks, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->cpr_enabled = true;
 | |
| +	cpr3_debug(ctrl, "CPR closed-loop operation enabled\n");
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_closed_loop_disable() - disable logical CPR closed-loop operation
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static inline int cpr3_closed_loop_disable(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	if (!ctrl->cpr_enabled) {
 | |
| +		/* Already disabled */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	cpr3_clock_disable(ctrl);
 | |
| +	ctrl->cpr_enabled = false;
 | |
| +	cpr3_debug(ctrl, "CPR closed-loop operation disabled\n");
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_regulator_get_gcnt() - returns the GCNT register value corresponding
 | |
| + *		to the clock rate and sensor time of the CPR3 controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: GCNT value
 | |
| + */
 | |
| +static u32 cpr3_regulator_get_gcnt(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	u64 temp;
 | |
| +	unsigned int remainder;
 | |
| +	u32 gcnt;
 | |
| +
 | |
| +	temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->sensor_time;
 | |
| +	remainder = do_div(temp, 1000000000);
 | |
| +	if (remainder)
 | |
| +		temp++;
 | |
| +	/*
 | |
| +	 * GCNT == 0 corresponds to a single ref clock measurement interval so
 | |
| +	 * offset GCNT values by 1.
 | |
| +	 */
 | |
| +	gcnt = temp - 1;
 | |
| +
 | |
| +	return gcnt;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_regulator_init_thread() - performs hardware initialization of CPR
 | |
| + *		thread registers
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + *
 | |
| + * CPR interface/bus clocks must be enabled before calling this function.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_regulator_init_thread(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	u32 reg;
 | |
| +
 | |
| +	reg = (thread->consecutive_up << CPR3_THRESH_CONS_UP_SHIFT)
 | |
| +		& CPR3_THRESH_CONS_UP_MASK;
 | |
| +	reg |= (thread->consecutive_down << CPR3_THRESH_CONS_DOWN_SHIFT)
 | |
| +		& CPR3_THRESH_CONS_DOWN_MASK;
 | |
| +	reg |= (thread->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT)
 | |
| +		& CPR3_THRESH_UP_THRESH_MASK;
 | |
| +	reg |= (thread->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT)
 | |
| +		& CPR3_THRESH_DOWN_THRESH_MASK;
 | |
| +
 | |
| +	cpr3_write(thread->ctrl, CPR3_REG_THRESH(thread->thread_id), reg);
 | |
| +
 | |
| +	/*
 | |
| +	 * Mask all RO's initially so that unused thread doesn't contribute
 | |
| +	 * to closed-loop voltage.
 | |
| +	 */
 | |
| +	cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
 | |
| +		CPR3_RO_MASK);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_regulator_init_temp_points() - performs hardware initialization of CPR4
 | |
| + *		registers to track tsen temperature data and also specify the
 | |
| + *		temperature band range values to apply different voltage margins
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * CPR interface/bus clocks must be enabled before calling this function.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_regulator_init_temp_points(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	if (!ctrl->allow_temp_adj)
 | |
| +		return 0;
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MISC,
 | |
| +				CPR4_MISC_TEMP_SENSOR_ID_START_MASK,
 | |
| +				ctrl->temp_sensor_id_start
 | |
| +				<< CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MISC,
 | |
| +				CPR4_MISC_TEMP_SENSOR_ID_END_MASK,
 | |
| +				ctrl->temp_sensor_id_end
 | |
| +				<< CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT2,
 | |
| +		CPR4_MARGIN_TEMP_POINT2_MASK,
 | |
| +		(ctrl->temp_band_count == 4 ? ctrl->temp_points[2] : 0x7FF)
 | |
| +		<< CPR4_MARGIN_TEMP_POINT2_SHIFT);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
 | |
| +		CPR4_MARGIN_TEMP_POINT1_MASK,
 | |
| +		(ctrl->temp_band_count >= 3 ? ctrl->temp_points[1] : 0x7FF)
 | |
| +		<< CPR4_MARGIN_TEMP_POINT1_SHIFT);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
 | |
| +		CPR4_MARGIN_TEMP_POINT0_MASK,
 | |
| +		(ctrl->temp_band_count >= 2 ? ctrl->temp_points[0] : 0x7FF)
 | |
| +		<< CPR4_MARGIN_TEMP_POINT0_SHIFT);
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_regulator_init_cpr4() - performs hardware initialization at the
 | |
| + *		controller and thread level required for CPR4 operation.
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * CPR interface/bus clocks must be enabled before calling this function.
 | |
| + * This function allocates sdelta structures and sdelta tables for aggregated
 | |
| + * corners of the controller and its threads.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct cpr3_thread *thread;
 | |
| +	struct cpr3_regulator *vreg;
 | |
| +	struct cpr4_sdelta *sdelta;
 | |
| +	int i, j, ctrl_max_core_count, thread_max_core_count, rc = 0;
 | |
| +	bool ctrl_valid_sdelta, thread_valid_sdelta;
 | |
| +	u32 pmic_step_size = 1;
 | |
| +	int thread_id = 0;
 | |
| +	u64 temp;
 | |
| +
 | |
| +	if (ctrl->supports_hw_closed_loop) {
 | |
| +		if (ctrl->saw_use_unit_mV)
 | |
| +			pmic_step_size = ctrl->step_volt / 1000;
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +				  CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
 | |
| +				  (pmic_step_size
 | |
| +				  << CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
 | |
| +
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
 | |
| +				  CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
 | |
| +				  (ctrl->down_error_step_limit
 | |
| +					<< CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
 | |
| +
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
 | |
| +				  CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
 | |
| +				  (ctrl->up_error_step_limit
 | |
| +					<< CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
 | |
| +
 | |
| +		/*
 | |
| +		 * Enable thread aggregation regardless of which threads are
 | |
| +		 * enabled or disabled.
 | |
| +		 */
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
 | |
| +				  CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
 | |
| +				  CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
 | |
| +
 | |
| +		switch (ctrl->thread_count) {
 | |
| +		case 0:
 | |
| +			/* Disable both threads */
 | |
| +			cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(0),
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
 | |
| +
 | |
| +			cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(1),
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
 | |
| +			break;
 | |
| +		case 1:
 | |
| +			/* Disable unused thread */
 | |
| +			thread_id = ctrl->thread[0].thread_id ? 0 : 1;
 | |
| +			cpr3_masked_write(ctrl,
 | |
| +				CPR4_REG_CPR_MASK_THREAD(thread_id),
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
 | |
| +				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
 | |
| +				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
 | |
| +			break;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (!ctrl->allow_core_count_adj && !ctrl->allow_temp_adj
 | |
| +		&& !ctrl->allow_boost) {
 | |
| +		/*
 | |
| +		 * Skip below configuration as none of the features
 | |
| +		 * are enabled.
 | |
| +		 */
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->supports_hw_closed_loop)
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +				  CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN,
 | |
| +				  CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +			CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK,
 | |
| +			ctrl->step_quot_fixed
 | |
| +			<< CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT);
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +			CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN,
 | |
| +			(ctrl->use_dynamic_step_quot
 | |
| +			? CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN : 0));
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +			CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK,
 | |
| +			ctrl->initial_temp_band
 | |
| +			<< CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT);
 | |
| +
 | |
| +	rc = cpr4_regulator_init_temp_points(ctrl);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "initialize temp points failed, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->voltage_settling_time) {
 | |
| +		/*
 | |
| +		 * Configure the settling timer used to account for
 | |
| +		 * one VDD supply step.
 | |
| +		 */
 | |
| +		temp = (u64)ctrl->cpr_clock_rate
 | |
| +				* (u64)ctrl->voltage_settling_time;
 | |
| +		do_div(temp, 1000000000);
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
 | |
| +			CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
 | |
| +			temp
 | |
| +		    << CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Allocate memory for cpr4_sdelta structure and sdelta table for
 | |
| +	 * controller aggregated corner by finding the maximum core count
 | |
| +	 * used by any cpr3 regulators.
 | |
| +	 */
 | |
| +	ctrl_max_core_count = 1;
 | |
| +	ctrl_valid_sdelta = false;
 | |
| +	for (i = 0; i < ctrl->thread_count; i++) {
 | |
| +		thread = &ctrl->thread[i];
 | |
| +
 | |
| +		/*
 | |
| +		 * Allocate memory for cpr4_sdelta structure and sdelta table
 | |
| +		 * for thread aggregated corner by finding the maximum core
 | |
| +		 * count used by any cpr3 regulators of the thread.
 | |
| +		 */
 | |
| +		thread_max_core_count = 1;
 | |
| +		thread_valid_sdelta = false;
 | |
| +		for (j = 0; j < thread->vreg_count; j++) {
 | |
| +			vreg = &thread->vreg[j];
 | |
| +			thread_max_core_count = max(thread_max_core_count,
 | |
| +							vreg->max_core_count);
 | |
| +			thread_valid_sdelta |= (vreg->allow_core_count_adj
 | |
| +							| vreg->allow_temp_adj
 | |
| +							| vreg->allow_boost);
 | |
| +		}
 | |
| +		if (thread_valid_sdelta) {
 | |
| +			sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta),
 | |
| +					GFP_KERNEL);
 | |
| +			if (!sdelta)
 | |
| +				return -ENOMEM;
 | |
| +
 | |
| +			sdelta->table = devm_kcalloc(ctrl->dev,
 | |
| +						thread_max_core_count
 | |
| +						* ctrl->temp_band_count,
 | |
| +						sizeof(*sdelta->table),
 | |
| +						GFP_KERNEL);
 | |
| +			if (!sdelta->table)
 | |
| +				return -ENOMEM;
 | |
| +
 | |
| +			sdelta->boost_table = devm_kcalloc(ctrl->dev,
 | |
| +						ctrl->temp_band_count,
 | |
| +						sizeof(*sdelta->boost_table),
 | |
| +						GFP_KERNEL);
 | |
| +			if (!sdelta->boost_table)
 | |
| +				return -ENOMEM;
 | |
| +
 | |
| +			thread->aggr_corner.sdelta = sdelta;
 | |
| +		}
 | |
| +
 | |
| +		ctrl_valid_sdelta |= thread_valid_sdelta;
 | |
| +		ctrl_max_core_count = max(ctrl_max_core_count,
 | |
| +						thread_max_core_count);
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl_valid_sdelta) {
 | |
| +		sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta), GFP_KERNEL);
 | |
| +		if (!sdelta)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		sdelta->table = devm_kcalloc(ctrl->dev, ctrl_max_core_count
 | |
| +					* ctrl->temp_band_count,
 | |
| +					sizeof(*sdelta->table), GFP_KERNEL);
 | |
| +		if (!sdelta->table)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		sdelta->boost_table = devm_kcalloc(ctrl->dev,
 | |
| +					ctrl->temp_band_count,
 | |
| +					sizeof(*sdelta->boost_table),
 | |
| +					GFP_KERNEL);
 | |
| +		if (!sdelta->boost_table)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		ctrl->aggr_corner.sdelta = sdelta;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_write_temp_core_margin() - programs hardware SDELTA registers with
 | |
| + *		the voltage margin adjustments that need to be applied for
 | |
| + *		different online core-count and temperature bands.
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + * @addr:		SDELTA register address
 | |
| + * @temp_core_adj:	Array of voltage margin values for different temperature
 | |
| + *			bands.
 | |
| + *
 | |
| + * CPR interface/bus clocks must be enabled before calling this function.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +static void cpr3_write_temp_core_margin(struct cpr3_controller *ctrl,
 | |
| +				 int addr, int *temp_core_adj)
 | |
| +{
 | |
| +	int i, margin_steps;
 | |
| +	u32 reg = 0;
 | |
| +
 | |
| +	for (i = 0; i < ctrl->temp_band_count; i++) {
 | |
| +		margin_steps = max(min(temp_core_adj[i], 127), -128);
 | |
| +		reg |= (margin_steps & CPR4_MARGIN_TEMP_CORE_ADJ_MASK) <<
 | |
| +			(i * CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT);
 | |
| +	}
 | |
| +
 | |
| +	cpr3_write(ctrl, addr, reg);
 | |
| +	cpr3_debug(ctrl, "sdelta offset=0x%08x, val=0x%08x\n", addr, reg);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_controller_program_sdelta() - programs hardware SDELTA registers with
 | |
| + *		the voltage margin adjustments that need to be applied at
 | |
| + *		different online core-count and temperature bands. Also,
 | |
| + *		programs hardware register configuration for per-online-core
 | |
| + *		and per-temperature based adjustments.
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * CPR interface/bus clocks must be enabled before calling this function.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct cpr3_corner *corner = &ctrl->aggr_corner;
 | |
| +	struct cpr4_sdelta *sdelta = corner->sdelta;
 | |
| +	int i, index, max_core_count, rc = 0;
 | |
| +	bool cpr_enabled = ctrl->cpr_enabled;
 | |
| +
 | |
| +	if (!sdelta)
 | |
| +		/* cpr4_sdelta not defined for current aggregated corner */
 | |
| +		return 0;
 | |
| +
 | |
| +	if (ctrl->supports_hw_closed_loop && ctrl->cpr_enabled) {
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +			CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
 | |
| +			(ctrl->use_hw_closed_loop && !sdelta->allow_boost)
 | |
| +			? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0);
 | |
| +	}
 | |
| +
 | |
| +	if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj
 | |
| +		&& !sdelta->allow_boost) {
 | |
| +		/*
 | |
| +		 * Per-online-core, per-temperature and voltage boost
 | |
| +		 * adjustments are disabled for this aggregation corner.
 | |
| +		 */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	/* Ensure that CPR clocks are enabled before writing to registers. */
 | |
| +	if (!cpr_enabled) {
 | |
| +		rc = cpr3_clock_enable(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +		ctrl->cpr_enabled = true;
 | |
| +	}
 | |
| +
 | |
| +	max_core_count = sdelta->max_core_count;
 | |
| +
 | |
| +	if (sdelta->allow_core_count_adj || sdelta->allow_temp_adj) {
 | |
| +		if (sdelta->allow_core_count_adj) {
 | |
| +			/* Program TEMP_CORE0 to same margins as TEMP_CORE1 */
 | |
| +			cpr3_write_temp_core_margin(ctrl,
 | |
| +				CPR4_REG_MARGIN_TEMP_CORE(0),
 | |
| +				&sdelta->table[0]);
 | |
| +		}
 | |
| +
 | |
| +		for (i = 0; i < max_core_count; i++) {
 | |
| +			index = i * sdelta->temp_band_count;
 | |
| +			/*
 | |
| +			 * Program TEMP_COREi with voltage margin adjustments
 | |
| +			 * that need to be applied when the number of cores
 | |
| +			 * becomes i.
 | |
| +			 */
 | |
| +			cpr3_write_temp_core_margin(ctrl,
 | |
| +				CPR4_REG_MARGIN_TEMP_CORE(
 | |
| +						sdelta->allow_core_count_adj
 | |
| +						? i + 1 : max_core_count),
 | |
| +						&sdelta->table[index]);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (sdelta->allow_boost) {
 | |
| +		/* Program only boost_num_cores row of SDELTA */
 | |
| +		cpr3_write_temp_core_margin(ctrl,
 | |
| +			CPR4_REG_MARGIN_TEMP_CORE(sdelta->boost_num_cores),
 | |
| +					&sdelta->boost_table[0]);
 | |
| +	}
 | |
| +
 | |
| +	if (!sdelta->allow_core_count_adj && !sdelta->allow_boost) {
 | |
| +		cpr3_masked_write(ctrl, CPR4_REG_MISC,
 | |
| +			CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
 | |
| +			max_core_count
 | |
| +			<< CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
 | |
| +	}
 | |
| +
 | |
| +	cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
 | |
| +		CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
 | |
| +		| CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
 | |
| +		| CPR4_MARGIN_ADJ_CTL_BOOST_EN,
 | |
| +		max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
 | |
| +		| ((sdelta->allow_core_count_adj || sdelta->allow_boost)
 | |
| +			? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
 | |
| +		| ((sdelta->allow_temp_adj && ctrl->supports_hw_closed_loop)
 | |
| +			? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
 | |
| +		| (((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
 | |
| +		    || !ctrl->supports_hw_closed_loop)
 | |
| +			? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
 | |
| +		| (sdelta->allow_boost
 | |
| +			?  CPR4_MARGIN_ADJ_CTL_BOOST_EN : 0));
 | |
| +
 | |
| +	/*
 | |
| +	 * Ensure that all previous CPR register writes have completed before
 | |
| +	 * continuing.
 | |
| +	 */
 | |
| +	mb();
 | |
| +
 | |
| +	/* Turn off CPR clocks if they were off before this function call. */
 | |
| +	if (!cpr_enabled) {
 | |
| +		cpr3_clock_disable(ctrl);
 | |
| +		ctrl->cpr_enabled = false;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_regulator_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 ||
 | |
| +		     ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
 | |
| +		    ctrl->vdd_limit_regulator) {
 | |
| +			rc = regulator_enable(ctrl->vdd_limit_regulator);
 | |
| +			if (rc) {
 | |
| +				cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
 | |
| +					rc);
 | |
| +				return rc;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_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_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)
 | |
| +				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)
 | |
| +					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 = 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) {
 | |
| +			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 && ctrl->supports_hw_closed_loop
 | |
| +	    && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		/*
 | |
| +		 * CPR4 features enforce voltage reprogramming when the last
 | |
| +		 * set voltage and new set voltage are same. This way, we can
 | |
| +		 * ensure that SAW PMIC STATUS register is updated with newly
 | |
| +		 * programmed voltage.
 | |
| +		 */
 | |
| +		rc = regulator_sync_voltage(vdd);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "regulator_sync_voltage(vdd) == %d failed, rc=%d\n",
 | |
| +				new_volt, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (new_volt >= last_volt) {
 | |
| +		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_max_sdelta_diff() - returns the maximum voltage difference in
 | |
| + *		microvolts that can result from different operating conditions
 | |
| + *		for the specified sdelta struct
 | |
| + * @sdelta:		Pointer to the sdelta structure
 | |
| + * @step_volt:		Step size in microvolts between available set
 | |
| + *			points of the VDD supply.
 | |
| + *
 | |
| + * Return: voltage difference between the highest and lowest adjustments if
 | |
| + *	sdelta and sdelta->table are valid, else 0.
 | |
| + */
 | |
| +static int cpr3_regulator_max_sdelta_diff(const struct cpr4_sdelta *sdelta,
 | |
| +				int step_volt)
 | |
| +{
 | |
| +	int i, j, index, sdelta_min = INT_MAX, sdelta_max = INT_MIN;
 | |
| +
 | |
| +	if (!sdelta || !sdelta->table)
 | |
| +		return 0;
 | |
| +
 | |
| +	for (i = 0; i < sdelta->max_core_count; i++) {
 | |
| +		for (j = 0; j < sdelta->temp_band_count; j++) {
 | |
| +			index = i * sdelta->temp_band_count + j;
 | |
| +			sdelta_min = min(sdelta_min, sdelta->table[index]);
 | |
| +			sdelta_max = max(sdelta_max, sdelta->table[index]);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return (sdelta_max - sdelta_min) * step_volt;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_regulator_aggregate_sdelta() - check open-loop voltages of current
 | |
| + *		aggregated corner and current corner of a given regulator
 | |
| + *		and adjust the sdelta strucuture data of aggregate corner.
 | |
| + * @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
 | |
| + * @step_volt:		Step size in microvolts between available set
 | |
| + *			points of the VDD supply.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +static void cpr3_regulator_aggregate_sdelta(
 | |
| +				struct cpr3_corner *aggr_corner,
 | |
| +				const struct cpr3_corner *corner, int step_volt)
 | |
| +{
 | |
| +	struct cpr4_sdelta *aggr_sdelta, *sdelta;
 | |
| +	int aggr_core_count, core_count, temp_band_count;
 | |
| +	u32 aggr_index, index;
 | |
| +	int i, j, sdelta_size, cap_steps, adjust_sdelta;
 | |
| +
 | |
| +	aggr_sdelta = aggr_corner->sdelta;
 | |
| +	sdelta = corner->sdelta;
 | |
| +
 | |
| +	if (aggr_corner->open_loop_volt < corner->open_loop_volt) {
 | |
| +		/*
 | |
| +		 * Found the new dominant regulator as its open-loop requirement
 | |
| +		 * is higher than previous dominant regulator. Calculate cap
 | |
| +		 * voltage to limit the SDELTA values to make sure the runtime
 | |
| +		 * (Core-count/temp) adjustments do not violate other
 | |
| +		 * regulators' voltage requirements. Use cpr4_sdelta values of
 | |
| +		 * new dominant regulator.
 | |
| +		 */
 | |
| +		aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
 | |
| +						(corner->open_loop_volt -
 | |
| +						aggr_corner->open_loop_volt));
 | |
| +
 | |
| +		/* Clear old data in the sdelta table */
 | |
| +		sdelta_size = aggr_sdelta->max_core_count
 | |
| +					* aggr_sdelta->temp_band_count;
 | |
| +
 | |
| +		if (aggr_sdelta->allow_core_count_adj
 | |
| +			|| aggr_sdelta->allow_temp_adj)
 | |
| +			memset(aggr_sdelta->table, 0, sdelta_size
 | |
| +					* sizeof(*aggr_sdelta->table));
 | |
| +
 | |
| +		if (sdelta->allow_temp_adj || sdelta->allow_core_count_adj) {
 | |
| +			/* Copy new data in sdelta table */
 | |
| +			sdelta_size = sdelta->max_core_count
 | |
| +						* sdelta->temp_band_count;
 | |
| +			if (sdelta->table)
 | |
| +				memcpy(aggr_sdelta->table, sdelta->table,
 | |
| +					sdelta_size * sizeof(*sdelta->table));
 | |
| +		}
 | |
| +
 | |
| +		if (sdelta->allow_boost) {
 | |
| +			memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
 | |
| +				sdelta->temp_band_count
 | |
| +				* sizeof(*sdelta->boost_table));
 | |
| +			aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
 | |
| +		} else if (aggr_sdelta->allow_boost) {
 | |
| +			for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
 | |
| +				adjust_sdelta = (corner->open_loop_volt
 | |
| +						- aggr_corner->open_loop_volt)
 | |
| +						/ step_volt;
 | |
| +				aggr_sdelta->boost_table[i] += adjust_sdelta;
 | |
| +				aggr_sdelta->boost_table[i]
 | |
| +					= min(aggr_sdelta->boost_table[i], 0);
 | |
| +			}
 | |
| +		}
 | |
| +
 | |
| +		aggr_corner->open_loop_volt = corner->open_loop_volt;
 | |
| +		aggr_sdelta->allow_temp_adj = sdelta->allow_temp_adj;
 | |
| +		aggr_sdelta->allow_core_count_adj
 | |
| +					= sdelta->allow_core_count_adj;
 | |
| +		aggr_sdelta->max_core_count = sdelta->max_core_count;
 | |
| +		aggr_sdelta->temp_band_count = sdelta->temp_band_count;
 | |
| +	} else if (aggr_corner->open_loop_volt > corner->open_loop_volt) {
 | |
| +		/*
 | |
| +		 * Adjust the cap voltage if the open-loop requirement of new
 | |
| +		 * regulator is the next highest.
 | |
| +		 */
 | |
| +		aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
 | |
| +						(aggr_corner->open_loop_volt
 | |
| +						- corner->open_loop_volt));
 | |
| +
 | |
| +		if (sdelta->allow_boost) {
 | |
| +			for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
 | |
| +				adjust_sdelta = (aggr_corner->open_loop_volt
 | |
| +						- corner->open_loop_volt)
 | |
| +						/ step_volt;
 | |
| +				aggr_sdelta->boost_table[i] =
 | |
| +					sdelta->boost_table[i] + adjust_sdelta;
 | |
| +				aggr_sdelta->boost_table[i]
 | |
| +					= min(aggr_sdelta->boost_table[i], 0);
 | |
| +			}
 | |
| +			aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
 | |
| +		}
 | |
| +	} else {
 | |
| +		/*
 | |
| +		 * Found another dominant regulator with same open-loop
 | |
| +		 * requirement. Make cap voltage to '0'. Disable core-count
 | |
| +		 * adjustments as we couldn't support for both regulators.
 | |
| +		 * Keep enable temp based adjustments if enabled for both
 | |
| +		 * regulators and choose mininum margin adjustment values
 | |
| +		 * between them.
 | |
| +		 */
 | |
| +		aggr_sdelta->cap_volt = 0;
 | |
| +		aggr_sdelta->allow_core_count_adj = false;
 | |
| +
 | |
| +		if (aggr_sdelta->allow_temp_adj
 | |
| +					&& sdelta->allow_temp_adj) {
 | |
| +			aggr_core_count = aggr_sdelta->max_core_count - 1;
 | |
| +			core_count = sdelta->max_core_count - 1;
 | |
| +			temp_band_count = sdelta->temp_band_count;
 | |
| +			for (j = 0; j < temp_band_count; j++) {
 | |
| +				aggr_index = aggr_core_count * temp_band_count
 | |
| +						+ j;
 | |
| +				index = core_count * temp_band_count + j;
 | |
| +				aggr_sdelta->table[aggr_index] =
 | |
| +					min(aggr_sdelta->table[aggr_index],
 | |
| +						sdelta->table[index]);
 | |
| +			}
 | |
| +		} else {
 | |
| +			aggr_sdelta->allow_temp_adj = false;
 | |
| +		}
 | |
| +
 | |
| +		if (sdelta->allow_boost) {
 | |
| +			memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
 | |
| +				sdelta->temp_band_count
 | |
| +				* sizeof(*sdelta->boost_table));
 | |
| +			aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Keep non-dominant clients boost enable state */
 | |
| +	aggr_sdelta->allow_boost |= sdelta->allow_boost;
 | |
| +	if (aggr_sdelta->allow_boost)
 | |
| +		aggr_sdelta->allow_core_count_adj = false;
 | |
| +
 | |
| +	if (aggr_sdelta->cap_volt && !(aggr_sdelta->cap_volt == INT_MAX)) {
 | |
| +		core_count = aggr_sdelta->max_core_count;
 | |
| +		temp_band_count = aggr_sdelta->temp_band_count;
 | |
| +		/*
 | |
| +		 * Convert cap voltage from uV to PMIC steps and use to limit
 | |
| +		 * sdelta margin adjustments.
 | |
| +		 */
 | |
| +		cap_steps = aggr_sdelta->cap_volt / step_volt;
 | |
| +		for (i = 0; i < core_count; i++)
 | |
| +			for (j = 0; j < temp_band_count; j++) {
 | |
| +				index = i * temp_band_count + j;
 | |
| +				aggr_sdelta->table[index] =
 | |
| +						min(aggr_sdelta->table[index],
 | |
| +							cap_steps);
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * 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.
 | |
| + * @step_volt:			Step size in microvolts between available set
 | |
| + *				points of the VDD supply.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +static void cpr3_regulator_aggregate_corners(struct cpr3_corner *aggr_corner,
 | |
| +			const struct cpr3_corner *corner, bool aggr_quot,
 | |
| +			int step_volt)
 | |
| +{
 | |
| +	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->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;
 | |
| +	aggr_corner->use_open_loop |= corner->use_open_loop;
 | |
| +
 | |
| +	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]);
 | |
| +	}
 | |
| +
 | |
| +	if (aggr_corner->sdelta && corner->sdelta
 | |
| +		&& (aggr_corner->sdelta->table
 | |
| +		|| aggr_corner->sdelta->boost_table)) {
 | |
| +		cpr3_regulator_aggregate_sdelta(aggr_corner, corner, step_volt);
 | |
| +	} else {
 | |
| +		aggr_corner->open_loop_volt
 | |
| +			= max(aggr_corner->open_loop_volt,
 | |
| +				corner->open_loop_volt);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * 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;
 | |
| +	struct cpr4_sdelta *sdelta;
 | |
| +	bool valid = false;
 | |
| +	bool thread_valid;
 | |
| +	int i, j, rc, new_volt, vdd_volt, dynamic_floor_volt, last_corner_volt;
 | |
| +	u32 reg_last_measurement = 0, sdelta_size;
 | |
| +	int *sdelta_table, *boost_table;
 | |
| +
 | |
| +	last_corner_volt = 0;
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		/*
 | |
| +		 * Save aggregated corner open-loop voltage which was programmed
 | |
| +		 * during last corner switch which is used when programming new
 | |
| +		 * aggregated corner open-loop voltage.
 | |
| +		 */
 | |
| +		last_corner_volt = ctrl->aggr_corner.open_loop_volt;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop &&
 | |
| +		ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
 | |
| +		reg_last_measurement
 | |
| +			= cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
 | |
| +
 | |
| +	aggr_corner.sdelta = ctrl->aggr_corner.sdelta;
 | |
| +	if (aggr_corner.sdelta) {
 | |
| +		sdelta = aggr_corner.sdelta;
 | |
| +		sdelta_table = sdelta->table;
 | |
| +		if (sdelta_table) {
 | |
| +			sdelta_size = sdelta->max_core_count *
 | |
| +					sdelta->temp_band_count;
 | |
| +			memset(sdelta_table, 0, sdelta_size
 | |
| +					* sizeof(*sdelta_table));
 | |
| +		}
 | |
| +
 | |
| +		boost_table = sdelta->boost_table;
 | |
| +		if (boost_table)
 | |
| +			memset(boost_table, 0, sdelta->temp_band_count
 | |
| +					* sizeof(*boost_table));
 | |
| +
 | |
| +		memset(sdelta, 0, sizeof(*sdelta));
 | |
| +		sdelta->table = sdelta_table;
 | |
| +		sdelta->cap_volt = INT_MAX;
 | |
| +		sdelta->boost_table = boost_table;
 | |
| +	}
 | |
| +
 | |
| +	/* Aggregate the requests of all threads */
 | |
| +	for (i = 0; i < ctrl->thread_count; i++) {
 | |
| +		thread = &ctrl->thread[i];
 | |
| +		thread_valid = false;
 | |
| +
 | |
| +		sdelta = thread->aggr_corner.sdelta;
 | |
| +		if (sdelta) {
 | |
| +			sdelta_table = sdelta->table;
 | |
| +			if (sdelta_table) {
 | |
| +				sdelta_size = sdelta->max_core_count *
 | |
| +						sdelta->temp_band_count;
 | |
| +				memset(sdelta_table, 0, sdelta_size
 | |
| +						* sizeof(*sdelta_table));
 | |
| +			}
 | |
| +
 | |
| +			boost_table = sdelta->boost_table;
 | |
| +			if (boost_table)
 | |
| +				memset(boost_table, 0, sdelta->temp_band_count
 | |
| +						* sizeof(*boost_table));
 | |
| +
 | |
| +			memset(sdelta, 0, sizeof(*sdelta));
 | |
| +			sdelta->table = sdelta_table;
 | |
| +			sdelta->cap_volt = INT_MAX;
 | |
| +			sdelta->boost_table = boost_table;
 | |
| +		}
 | |
| +
 | |
| +		memset(&thread->aggr_corner, 0, sizeof(thread->aggr_corner));
 | |
| +		thread->aggr_corner.sdelta = sdelta;
 | |
| +		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, ctrl->step_volt);
 | |
| +		}
 | |
| +
 | |
| +		valid |= thread_valid;
 | |
| +
 | |
| +		if (thread_valid)
 | |
| +			cpr3_regulator_aggregate_corners(&aggr_corner,
 | |
| +					&thread->aggr_corner,
 | |
| +					false, ctrl->step_volt);
 | |
| +	}
 | |
| +
 | |
| +	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) {
 | |
| +		/*
 | |
| +		 * Always program open-loop voltage for CPR4 controllers which
 | |
| +		 * support hardware closed-loop.  Storing the last closed loop
 | |
| +		 * voltage in corner structure can still help with debugging.
 | |
| +		 */
 | |
| +		if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
 | |
| +			new_volt = aggr_corner.last_volt;
 | |
| +		else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
 | |
| +			 && ctrl->supports_hw_closed_loop)
 | |
| +			new_volt = aggr_corner.open_loop_volt;
 | |
| +		else
 | |
| +			new_volt = min(aggr_corner.last_volt +
 | |
| +			      cpr3_regulator_max_sdelta_diff(aggr_corner.sdelta,
 | |
| +							     ctrl->step_volt),
 | |
| +				       aggr_corner.ceiling_volt);
 | |
| +
 | |
| +		aggr_corner.last_volt = new_volt;
 | |
| +	} else {
 | |
| +		new_volt = aggr_corner.open_loop_volt;
 | |
| +		aggr_corner.last_volt = aggr_corner.open_loop_volt;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
 | |
| +	    && ctrl->supports_hw_closed_loop) {
 | |
| +		/*
 | |
| +		 * Store last aggregated corner open-loop voltage in vdd_volt
 | |
| +		 * which is used when programming current aggregated corner
 | |
| +		 * required voltage.
 | |
| +		 */
 | |
| +		vdd_volt = last_corner_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();
 | |
| +	} else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
 | |
| +		   && ctrl->vdd_limit_regulator) {
 | |
| +		/* 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;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	ctrl->aggr_corner = aggr_corner;
 | |
| +
 | |
| +	if (ctrl->allow_core_count_adj || ctrl->allow_temp_adj
 | |
| +		|| ctrl->allow_boost) {
 | |
| +		rc = cpr3_controller_program_sdelta(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to program sdelta, rc=%d\n", rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * 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 &&
 | |
| +			!aggr_corner.use_open_loop)
 | |
| +		cpr3_ctrl_loop_enable(ctrl);
 | |
| +
 | |
| +	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",
 | |
| +				div_s64(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 ro_mask_restore, cont_dly_restore, up_down_dly_restore = 0;
 | |
| +	int quot_delta, quot_delta_scaled, quot_delta_scaled_sum;
 | |
| +	int *quot_delta_results;
 | |
| +	int rc, rc2, 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;
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +			kfree(quot_delta_results);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +
 | |
| +	/* Unmask all RO's */
 | |
| +	ro_mask_restore = cpr3_read(ctrl, CPR3_REG_RO_MASK(0));
 | |
| +	cpr3_write(ctrl, CPR3_REG_RO_MASK(0), 0);
 | |
| +
 | |
| +	/*
 | |
| +	 * 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);
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc2 = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc2) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc2);
 | |
| +			rc = rc2;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	cpr3_ctrl_loop_disable(ctrl);
 | |
| +
 | |
| +	cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_restore);
 | |
| +
 | |
| +	cpr3_write(ctrl, CPR3_REG_RO_MASK(0), ro_mask_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_volt_and_quot() - readjust the target quotients as
 | |
| + *		well as the floor, ceiling, and open-loop voltages 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.
 | |
| + *
 | |
| + * Return: None
 | |
| + */
 | |
| +static void cpr3_regulator_readjust_volt_and_quot(struct cpr3_regulator *vreg,
 | |
| +		int old_adjust_volt, int new_adjust_volt)
 | |
| +{
 | |
| +	unsigned long long temp;
 | |
| +	int i, j, old_volt, new_volt, rounded_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);
 | |
| +			}
 | |
| +		}
 | |
| +
 | |
| +		rounded_volt = CPR3_ROUND(new_volt,
 | |
| +					vreg->thread->ctrl->step_volt);
 | |
| +
 | |
| +		if (!vreg->aging_allow_open_loop_adj)
 | |
| +			rounded_volt = 0;
 | |
| +
 | |
| +		vreg->corner[i].ceiling_volt
 | |
| +			= vreg->corner[i].unaged_ceiling_volt + rounded_volt;
 | |
| +		vreg->corner[i].ceiling_volt = min(vreg->corner[i].ceiling_volt,
 | |
| +					      vreg->corner[i].abs_ceiling_volt);
 | |
| +		vreg->corner[i].floor_volt
 | |
| +			= vreg->corner[i].unaged_floor_volt + rounded_volt;
 | |
| +		vreg->corner[i].floor_volt = min(vreg->corner[i].floor_volt,
 | |
| +						vreg->corner[i].ceiling_volt);
 | |
| +		vreg->corner[i].open_loop_volt
 | |
| +			= vreg->corner[i].unaged_open_loop_volt + rounded_volt;
 | |
| +		vreg->corner[i].open_loop_volt
 | |
| +			= min(vreg->corner[i].open_loop_volt,
 | |
| +				vreg->corner[i].ceiling_volt);
 | |
| +
 | |
| +		vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
 | |
| +
 | |
| +		cpr3_debug(vreg, "corner %d: applying %d uV closed-loop and %d uV open-loop voltage margin adjustment\n",
 | |
| +			i, new_volt, rounded_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)
 | |
| +{
 | |
| +	struct cpr3_regulator *vreg;
 | |
| +	int i, j;
 | |
| +
 | |
| +	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_regulator_readjust_volt_and_quot(vreg,
 | |
| +				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 = 0;
 | |
| +	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;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Verify that the aging possible register (if specified) has an
 | |
| +	 * acceptable value.
 | |
| +	 */
 | |
| +	if (ctrl->aging_possible_reg) {
 | |
| +		reg = readl_relaxed(ctrl->aging_possible_reg);
 | |
| +		reg &= ctrl->aging_possible_mask;
 | |
| +		if (reg != ctrl->aging_possible_val)
 | |
| +			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 */
 | |
| +	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",
 | |
| +				j, rc);
 | |
| +			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;
 | |
| +
 | |
| +	if (vreg->cpr4_regulator_data != NULL)
 | |
| +		if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
 | |
| +			vreg->cpr4_regulator_data->mem_acc_funcs->set_mem_acc(rdev);
 | |
| +
 | |
| +	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;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->cpr4_regulator_data != NULL)
 | |
| +		if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
 | |
| +			vreg->cpr4_regulator_data->mem_acc_funcs->clear_mem_acc(rdev);
 | |
| +
 | |
| +	cpr3_debug(vreg, "set corner=%d\n", corner);
 | |
| +done:
 | |
| +	mutex_unlock(&ctrl->lock);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_handle_temp_open_loop_adjustment() - voltage based cold temperature
 | |
| + *
 | |
| + * @rdev:		Regulator device pointer for the cpr3-regulator
 | |
| + * @is_cold:		Flag to denote enter/exit cold condition
 | |
| + *
 | |
| + * This function is adjusts voltage margin based on cold condition
 | |
| + *
 | |
| + * Return: 0 = success
 | |
| + */
 | |
| +
 | |
| +int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
 | |
| +								bool is_cold)
 | |
| +{
 | |
| +	int i ,j, k, rc;
 | |
| +	struct cpr3_regulator *vreg;
 | |
| +
 | |
| +	mutex_lock(&ctrl->lock);
 | |
| +	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].open_loop_volt = is_cold ?
 | |
| +				    vreg->corner[k].cold_temp_open_loop_volt :
 | |
| +				    vreg->corner[k].normal_temp_open_loop_volt;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +	rc = cpr3_regulator_update_ctrl_state(ctrl);
 | |
| +	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_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;
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +	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,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * 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 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:
 | |
| +	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, &vreg->rdesc);
 | |
| +	if (!init_data) {
 | |
| +		cpr3_err(vreg, "regulator init data is missing\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	init_data->constraints.input_uV = init_data->constraints.max_uV;
 | |
| +	rdesc			= &vreg->rdesc;
 | |
| +	init_data->constraints.valid_ops_mask |=
 | |
| +		REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
 | |
| +	rdesc->ops = &cpr3_regulator_ops;
 | |
| +
 | |
| +	rdesc->n_voltages	= vreg->corner_count;
 | |
| +	rdesc->name		= init_data->constraints.name;
 | |
| +	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(config.dev, 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");
 | |
| +
 | |
| +/**
 | |
| + * 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;
 | |
| +	}
 | |
| +
 | |
| +	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 for CPR3 and CPR4 controllers and between open-loop
 | |
| + *		and full hardware closed-loop operation for CPRh controllers.
 | |
| + * @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;
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +			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 && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
 | |
| +		rc = regulator_enable(ctrl->vdd_limit_regulator);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	} else if (!ctrl->use_hw_closed_loop
 | |
| +			&& ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
 | |
| +		rc = regulator_disable(ctrl->vdd_limit_regulator);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "CPR limit regulator disable failed, 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, "CPR mode=%s\n",
 | |
| +		   use_hw_closed_loop ?
 | |
| +		   "HW closed-loop" : "SW closed-loop");
 | |
| +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);
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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;
 | |
| +	bool init_aging;
 | |
| +
 | |
| +	vreg->current_corner = CPR3_REGULATOR_CORNER_INVALID;
 | |
| +	vreg->last_closed_loop_corner = CPR3_REGULATOR_CORNER_INVALID;
 | |
| +
 | |
| +	init_aging = vreg->aging_allowed && vreg->thread->ctrl->aging_required;
 | |
| +
 | |
| +	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 (init_aging) {
 | |
| +			vreg->corner[i].unaged_floor_volt
 | |
| +				= vreg->corner[i].floor_volt;
 | |
| +			vreg->corner[i].unaged_ceiling_volt
 | |
| +				= vreg->corner[i].ceiling_volt;
 | |
| +			vreg->corner[i].unaged_open_loop_volt
 | |
| +				= vreg->corner[i].open_loop_volt;
 | |
| +		}
 | |
| +
 | |
| +		if (vreg->aging_allowed) {
 | |
| +			if (vreg->corner[i].unaged_floor_volt <= 0) {
 | |
| +				cpr3_err(vreg, "invalid unaged_floor_volt[%d] = %d\n",
 | |
| +					i, vreg->corner[i].unaged_floor_volt);
 | |
| +				return -EINVAL;
 | |
| +			}
 | |
| +			if (vreg->corner[i].unaged_ceiling_volt <= 0) {
 | |
| +				cpr3_err(vreg, "invalid unaged_ceiling_volt[%d] = %d\n",
 | |
| +					i, vreg->corner[i].unaged_ceiling_volt);
 | |
| +				return -EINVAL;
 | |
| +			}
 | |
| +			if (vreg->corner[i].unaged_open_loop_volt <= 0) {
 | |
| +				cpr3_err(vreg, "invalid unaged_open_loop_volt[%d] = %d\n",
 | |
| +				      i, vreg->corner[i].unaged_open_loop_volt);
 | |
| +				return -EINVAL;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +			mutex_unlock(&ctrl->lock);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	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)
 | |
| +{
 | |
| +	struct cpr3_thread *thread;
 | |
| +	struct cpr3_regulator *vreg;
 | |
| +	int i, j, allow_boost_vreg_count = 0;
 | |
| +
 | |
| +	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;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < ctrl->thread_count; i++) {
 | |
| +		thread = &ctrl->thread[i];
 | |
| +		for (j = 0; j < thread->vreg_count; j++) {
 | |
| +			vreg = &thread->vreg[j];
 | |
| +			if (vreg->allow_boost)
 | |
| +				allow_boost_vreg_count++;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (allow_boost_vreg_count > 1) {
 | |
| +		/*
 | |
| +		 * Boost feature is not allowed to be used for more
 | |
| +		 * than one CPR3 regulator of a CPR3 controller.
 | |
| +		 */
 | |
| +		cpr3_err(ctrl, "Boost feature is enabled for more than one regulator\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_panic_callback() - panic notification callback function. This function
 | |
| + *		is invoked when a kernel panic occurs.
 | |
| + * @nfb:	Notifier block pointer of CPR3 controller
 | |
| + * @event:	Value passed unmodified to notifier function
 | |
| + * @data:	Pointer passed unmodified to notifier function
 | |
| + *
 | |
| + * Return: NOTIFY_OK
 | |
| + */
 | |
| +static int cpr3_panic_callback(struct notifier_block *nfb,
 | |
| +			unsigned long event, void *data)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = container_of(nfb,
 | |
| +				struct cpr3_controller, panic_notifier);
 | |
| +	struct cpr3_panic_regs_info *regs_info = ctrl->panic_regs_info;
 | |
| +	struct cpr3_reg_info *reg;
 | |
| +	int i = 0;
 | |
| +
 | |
| +	for (i = 0; i < regs_info->reg_count; i++) {
 | |
| +		reg = &(regs_info->regs[i]);
 | |
| +		reg->value = readl_relaxed(reg->virt_addr);
 | |
| +		pr_err("%s[0x%08x] = 0x%08x\n", reg->name, reg->addr,
 | |
| +			reg->value);
 | |
| +	}
 | |
| +	/*
 | |
| +	 * Barrier to ensure that the information has been updated in the
 | |
| +	 * structure.
 | |
| +	 */
 | |
| +	mb();
 | |
| +
 | |
| +	return NOTIFY_OK;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * 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));
 | |
| +
 | |
| +	if (ctrl->aging_possible_mask) {
 | |
| +		/*
 | |
| +		 * Aging possible register address is required if an aging
 | |
| +		 * possible mask has been specified.
 | |
| +		 */
 | |
| +		res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
 | |
| +						"aging_allowed");
 | |
| +		if (!res || !res->start) {
 | |
| +			cpr3_err(ctrl, "CPR aging allowed address is missing\n");
 | |
| +			return -ENXIO;
 | |
| +		}
 | |
| +		ctrl->aging_possible_reg = 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) {
 | |
| +		if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
 | |
| +			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 &&
 | |
| +	    ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
 | |
| +		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);
 | |
| +
 | |
| +	if (ctrl->panic_regs_info) {
 | |
| +		/* Register panic notification call back */
 | |
| +		ctrl->panic_notifier.notifier_call = cpr3_panic_callback;
 | |
| +		atomic_notifier_chain_register(&panic_notifier_list,
 | |
| +			&ctrl->panic_notifier);
 | |
| +	}
 | |
| +
 | |
| +	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_open_loop_regulator_register() - register the regulators for a CPR3
 | |
| + *			controller which will always work in Open loop and
 | |
| + *			won't support close loop.
 | |
| + * @pdev:		Platform device pointer for the CPR3 controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_open_loop_regulator_register(struct platform_device *pdev,
 | |
| +				      struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct cpr3_regulator *vreg;
 | |
| +	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;
 | |
| +	}
 | |
| +
 | |
| +	if (!ctrl->vdd_regulator) {
 | |
| +		cpr3_err(ctrl, "vdd regulator missing\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	mutex_init(&ctrl->lock);
 | |
| +
 | |
| +	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++) {
 | |
| +			vreg = &ctrl->thread[i].vreg[j];
 | |
| +			vreg->corner[i].last_volt =
 | |
| +				vreg->corner[i].open_loop_volt;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* 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;
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	mutex_lock(&cpr3_controller_list_mutex);
 | |
| +	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, rc = 0;
 | |
| +
 | |
| +	mutex_lock(&cpr3_controller_list_mutex);
 | |
| +	list_del(&ctrl->list);
 | |
| +	cpr3_regulator_debugfs_ctrl_remove(ctrl);
 | |
| +	mutex_unlock(&cpr3_controller_list_mutex);
 | |
| +
 | |
| +	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
 | |
| +		rc = cpr3_ctrl_clear_cpr4_config(ctrl);
 | |
| +		if (rc)
 | |
| +			cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
 | |
| +				rc);
 | |
| +	}
 | |
| +
 | |
| +	cpr3_ctrl_loop_disable(ctrl);
 | |
| +
 | |
| +	cpr3_closed_loop_disable(ctrl);
 | |
| +
 | |
| +	if (ctrl->vdd_limit_regulator) {
 | |
| +		regulator_disable(ctrl->vdd_limit_regulator);
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +
 | |
| +	if (ctrl->panic_notifier.notifier_call)
 | |
| +		atomic_notifier_chain_unregister(&panic_notifier_list,
 | |
| +			&ctrl->panic_notifier);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_open_loop_regulator_unregister() - unregister the regulators for a CPR3
 | |
| + *			open loop controller and perform CPR hardware shutdown
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int i, j;
 | |
| +
 | |
| +	mutex_lock(&cpr3_controller_list_mutex);
 | |
| +	list_del(&ctrl->list);
 | |
| +	mutex_unlock(&cpr3_controller_list_mutex);
 | |
| +
 | |
| +	if (ctrl->vdd_limit_regulator) {
 | |
| +		regulator_disable(ctrl->vdd_limit_regulator);
 | |
| +	}
 | |
| +
 | |
| +	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);
 | |
| +
 | |
| +	if (ctrl->panic_notifier.notifier_call)
 | |
| +		atomic_notifier_chain_unregister(&panic_notifier_list,
 | |
| +			&ctrl->panic_notifier);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| --- /dev/null
 | |
| +++ b/drivers/regulator/cpr3-regulator.h
 | |
| @@ -0,0 +1,1211 @@
 | |
| +/*
 | |
| + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 and
 | |
| + * only version 2 as published by the Free Software Foundation.
 | |
| + *
 | |
| + * This program is distributed in the hope that it will be useful,
 | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| + * GNU General Public License for more details.
 | |
| + */
 | |
| +
 | |
| +#ifndef __REGULATOR_CPR3_REGULATOR_H__
 | |
| +#define __REGULATOR_CPR3_REGULATOR_H__
 | |
| +
 | |
| +#include <linux/clk.h>
 | |
| +#include <linux/mutex.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/types.h>
 | |
| +#include <linux/power/qcom/apm.h>
 | |
| +#include <linux/regulator/driver.h>
 | |
| +
 | |
| +struct cpr3_controller;
 | |
| +struct cpr3_thread;
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_fuse_param - defines one contiguous segment of a fuse parameter
 | |
| + *			    that is contained within a given row.
 | |
| + * @row:	Fuse row number
 | |
| + * @bit_start:	The first bit within the row of the fuse parameter segment
 | |
| + * @bit_end:	The last bit within the row of the fuse parameter segment
 | |
| + *
 | |
| + * Each fuse row is 64 bits in length.  bit_start and bit_end may take values
 | |
| + * from 0 to 63.  bit_start must be less than or equal to bit_end.
 | |
| + */
 | |
| +struct cpr3_fuse_param {
 | |
| +	unsigned		row;
 | |
| +	unsigned		bit_start;
 | |
| +	unsigned		bit_end;
 | |
| +};
 | |
| +
 | |
| +/* Each CPR3 sensor has 16 ring oscillators */
 | |
| +#define CPR3_RO_COUNT		16
 | |
| +
 | |
| +/* The maximum number of sensors that can be present on a single CPR loop. */
 | |
| +#define CPR3_MAX_SENSOR_COUNT	256
 | |
| +
 | |
| +/* This constant is used when allocating array printing buffers. */
 | |
| +#define MAX_CHARS_PER_INT	10
 | |
| +
 | |
| +/**
 | |
| + * struct cpr4_sdelta - CPR4 controller specific data structure for the sdelta
 | |
| + *			adjustment table which is used to adjust the VDD supply
 | |
| + *			voltage automatically based upon the temperature and/or
 | |
| + *			the number of online CPU cores.
 | |
| + * @allow_core_count_adj: Core count adjustments are allowed.
 | |
| + * @allow_temp_adj:	Temperature based adjustments are allowed.
 | |
| + * @max_core_count:	Maximum number of cores considered for core count
 | |
| + *			adjustment logic.
 | |
| + * @temp_band_count:	Number of temperature bands considered for temperature
 | |
| + *			based adjustment logic.
 | |
| + * @cap_volt:		CAP in uV to apply to SDELTA margins with multiple
 | |
| + *			cpr3-regulators defined for single controller.
 | |
| + * @table:		SDELTA table with per-online-core and temperature based
 | |
| + *			adjustments of size (max_core_count * temp_band_count)
 | |
| + *			Outer: core count
 | |
| + *			Inner: temperature band
 | |
| + *			Each element has units of VDD supply steps. Positive
 | |
| + *			values correspond to a reduction in voltage and negative
 | |
| + *			value correspond to an increase (this follows the SDELTA
 | |
| + *			register semantics).
 | |
| + * @allow_boost:	Voltage boost allowed.
 | |
| + * @boost_num_cores:	The number of online cores at which the boost voltage
 | |
| + *			adjustments will be applied
 | |
| + * @boost_table:	SDELTA table with boost voltage adjustments of size
 | |
| + *			temp_band_count. Each element has units of VDD supply
 | |
| + *			steps. Positive values correspond to a reduction in
 | |
| + *			voltage and negative value correspond to an increase
 | |
| + *			(this follows the SDELTA register semantics).
 | |
| + */
 | |
| +struct cpr4_sdelta {
 | |
| +	bool	allow_core_count_adj;
 | |
| +	bool	allow_temp_adj;
 | |
| +	int	max_core_count;
 | |
| +	int	temp_band_count;
 | |
| +	int	cap_volt;
 | |
| +	int	*table;
 | |
| +	bool	allow_boost;
 | |
| +	int	boost_num_cores;
 | |
| +	int	*boost_table;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_corner - CPR3 virtual voltage corner data structure
 | |
| + * @floor_volt:		CPR closed-loop floor voltage in microvolts
 | |
| + * @ceiling_volt:	CPR closed-loop ceiling voltage in microvolts
 | |
| + * @open_loop_volt:	CPR open-loop voltage (i.e. initial voltage) in
 | |
| + *			microvolts
 | |
| + * @last_volt:		Last known settled CPR closed-loop voltage which is used
 | |
| + *			when switching to a new corner
 | |
| + * @abs_ceiling_volt:	The absolute CPR closed-loop ceiling voltage in
 | |
| + *			microvolts.  This is used to limit the ceiling_volt
 | |
| + *			value when it is increased as a result of aging
 | |
| + *			adjustment.
 | |
| + * @unaged_floor_volt:	The CPR closed-loop floor voltage in microvolts before
 | |
| + *			any aging adjustment is performed
 | |
| + * @unaged_ceiling_volt: The CPR closed-loop ceiling voltage in microvolts
 | |
| + *			before any aging adjustment is performed
 | |
| + * @unaged_open_loop_volt: The CPR open-loop voltage (i.e. initial voltage) in
 | |
| + *			microvolts before any aging adjusment is performed
 | |
| + * @system_volt:	The system-supply voltage in microvolts or corners or
 | |
| + *			levels
 | |
| + * @mem_acc_volt:	The mem-acc-supply voltage in corners
 | |
| + * @proc_freq:		Processor frequency in Hertz. For CPR rev. 3 and 4
 | |
| + *			conrollers, this field is only used by platform specific
 | |
| + *			CPR3 driver for interpolation. For CPRh-compliant
 | |
| + *			controllers, this frequency is also utilized by the
 | |
| + *			clock driver to determine the corner to CPU clock
 | |
| + *			frequency mappings.
 | |
| + * @cpr_fuse_corner:	Fused corner index associated with this virtual corner
 | |
| + *			(only used by platform specific CPR3 driver for
 | |
| + *			mapping purposes)
 | |
| + * @target_quot:	Array of target quotient values to use for each ring
 | |
| + *			oscillator (RO) for this corner.  A value of 0 should be
 | |
| + *			specified as the target quotient for each RO that is
 | |
| + *			unused by this corner.
 | |
| + * @ro_scale:		Array of CPR ring oscillator (RO) scaling factors.  The
 | |
| + *			scaling factor for each RO is defined from RO0 to RO15
 | |
| + *			with units of QUOT/V.  A value of 0 may be specified for
 | |
| + *			an RO that is unused.
 | |
| + * @ro_mask:		Bitmap where each of the 16 LSBs indicate if the
 | |
| + *			corresponding ROs should be masked for this corner
 | |
| + * @irq_en:		Bitmap of the CPR interrupts to enable for this corner
 | |
| + * @aging_derate:	The amount to derate the aging voltage adjustment
 | |
| + *			determined for the reference corner in units of uV/mV.
 | |
| + *			E.g. a value of 900 would imply that the adjustment for
 | |
| + *			this corner should be 90% (900/1000) of that for the
 | |
| + *			reference corner.
 | |
| + * @use_open_loop:	Boolean indicating that open-loop (i.e CPR disabled) as
 | |
| + *			opposed to closed-loop operation must be used for this
 | |
| + *			corner on CPRh controllers.
 | |
| + * @sdelta:		The CPR4 controller specific data for this corner. This
 | |
| + *			field is applicable for CPR4 controllers.
 | |
| + *
 | |
| + * The value of last_volt is initialized inside of the cpr3_regulator_register()
 | |
| + * call with the open_loop_volt value.  It can later be updated to the settled
 | |
| + * VDD supply voltage.  The values for unaged_floor_volt, unaged_ceiling_volt,
 | |
| + * and unaged_open_loop_volt are initialized inside of cpr3_regulator_register()
 | |
| + * if ctrl->aging_required == true.  These three values must be pre-initialized
 | |
| + * if cpr3_regulator_register() is called with ctrl->aging_required == false and
 | |
| + * ctrl->aging_succeeded == true.
 | |
| + *
 | |
| + * The values of ro_mask and irq_en are initialized inside of the
 | |
| + * cpr3_regulator_register() call.
 | |
| + */
 | |
| +struct cpr3_corner {
 | |
| +	int			floor_volt;
 | |
| +	int			ceiling_volt;
 | |
| +	int			cold_temp_open_loop_volt;
 | |
| +	int			normal_temp_open_loop_volt;
 | |
| +	int			open_loop_volt;
 | |
| +	int			last_volt;
 | |
| +	int			abs_ceiling_volt;
 | |
| +	int			unaged_floor_volt;
 | |
| +	int			unaged_ceiling_volt;
 | |
| +	int			unaged_open_loop_volt;
 | |
| +	int			system_volt;
 | |
| +	int			mem_acc_volt;
 | |
| +	u32			proc_freq;
 | |
| +	int			cpr_fuse_corner;
 | |
| +	u32			target_quot[CPR3_RO_COUNT];
 | |
| +	u32			ro_scale[CPR3_RO_COUNT];
 | |
| +	u32			ro_mask;
 | |
| +	u32			irq_en;
 | |
| +	int			aging_derate;
 | |
| +	bool			use_open_loop;
 | |
| +	struct cpr4_sdelta	*sdelta;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cprh_corner_band - CPRh controller specific data structure which
 | |
| + *			encapsulates the range of corners and the SDELTA
 | |
| + *			adjustment table to be applied to the corners within
 | |
| + *			the min and max bounds of the corner band.
 | |
| + * @corner:		Corner number which defines the corner band boundary
 | |
| + * @sdelta:		The SDELTA adjustment table which contains core-count
 | |
| + *			and temp based margin adjustments that are applicable
 | |
| + *			to the corner band.
 | |
| + */
 | |
| +struct cprh_corner_band {
 | |
| +	int			corner;
 | |
| +	struct cpr4_sdelta	*sdelta;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_fuse_parameters - CPR4 fuse specific data structure which has
 | |
| + * 			the required fuse parameters need for Close Loop CPR
 | |
| + * @(*apss_ro_sel_param)[2]:       Pointer to RO select fuse details
 | |
| + * @(*apss_init_voltage_param)[2]: Pointer to Target voltage fuse details
 | |
| + * @(*apss_target_quot_param)[2]:  Pointer to Target quot fuse details
 | |
| + * @(*apss_quot_offset_param)[2]:  Pointer to quot offset fuse details
 | |
| + * @cpr_fusing_rev_param:          Pointer to CPR revision fuse details
 | |
| + * @apss_speed_bin_param:          Pointer to Speed bin fuse details
 | |
| + * @cpr_boost_fuse_cfg_param:      Pointer to Boost fuse cfg details
 | |
| + * @apss_boost_fuse_volt_param:    Pointer to Boost fuse volt details
 | |
| + * @misc_fuse_volt_adj_param:      Pointer to Misc fuse volt fuse details
 | |
| + */
 | |
| +struct cpr3_fuse_parameters {
 | |
| +	struct cpr3_fuse_param (*apss_ro_sel_param)[2];
 | |
| +	struct cpr3_fuse_param (*apss_init_voltage_param)[2];
 | |
| +	struct cpr3_fuse_param (*apss_target_quot_param)[2];
 | |
| +	struct cpr3_fuse_param (*apss_quot_offset_param)[2];
 | |
| +	struct cpr3_fuse_param *cpr_fusing_rev_param;
 | |
| +	struct cpr3_fuse_param *apss_speed_bin_param;
 | |
| +	struct cpr3_fuse_param *cpr_boost_fuse_cfg_param;
 | |
| +	struct cpr3_fuse_param *apss_boost_fuse_volt_param;
 | |
| +	struct cpr3_fuse_param *misc_fuse_volt_adj_param;
 | |
| +};
 | |
| +
 | |
| +struct cpr4_mem_acc_func {
 | |
| +	void (*set_mem_acc)(struct regulator_dev *);
 | |
| +	void (*clear_mem_acc)(struct regulator_dev *);
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr4_reg_data - CPR4 regulator specific data structure which is
 | |
| + * target specific
 | |
| + * @cpr_valid_fuse_count: Number of valid fuse corners
 | |
| + * @fuse_ref_volt: 	  Pointer to fuse reference voltage
 | |
| + * @fuse_step_volt: 	  CPR step voltage available in fuse
 | |
| + * @cpr_clk_rate: 	  CPR clock rate
 | |
| + * @boost_fuse_ref_volt:  Boost fuse reference voltage
 | |
| + * @boost_ceiling_volt:   Boost ceiling voltage
 | |
| + * @boost_floor_volt: 	  Boost floor voltage
 | |
| + * @cpr3_fuse_params:     Pointer to CPR fuse parameters
 | |
| + * @mem_acc_funcs:        Pointer to MEM ACC set/clear functions
 | |
| + **/
 | |
| +struct cpr4_reg_data {
 | |
| +	u32 cpr_valid_fuse_count;
 | |
| +	int *fuse_ref_volt;
 | |
| +	u32 fuse_step_volt;
 | |
| +	u32 cpr_clk_rate;
 | |
| +	int boost_fuse_ref_volt;
 | |
| +	int boost_ceiling_volt;
 | |
| +	int boost_floor_volt;
 | |
| +	struct cpr3_fuse_parameters *cpr3_fuse_params;
 | |
| +	struct cpr4_mem_acc_func *mem_acc_funcs;
 | |
| +};
 | |
| +/**
 | |
| + * struct cpr3_reg_data - CPR3 regulator specific data structure which is
 | |
| + * target specific
 | |
| + * @cpr_valid_fuse_count: Number of valid fuse corners
 | |
| + * @(*init_voltage_param)[2]: Pointer to Target voltage fuse details
 | |
| + * @fuse_ref_volt: 	  Pointer to fuse reference voltage
 | |
| + * @fuse_step_volt: 	  CPR step voltage available in fuse
 | |
| + * @cpr_clk_rate: 	  CPR clock rate
 | |
| + * @cpr3_fuse_params:     Pointer to CPR fuse parameters
 | |
| + **/
 | |
| +struct cpr3_reg_data {
 | |
| +	u32 cpr_valid_fuse_count;
 | |
| +	struct cpr3_fuse_param (*init_voltage_param)[2];
 | |
| +	int *fuse_ref_volt;
 | |
| +	u32 fuse_step_volt;
 | |
| +	u32 cpr_clk_rate;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_regulator - CPR3 logical regulator instance associated with a
 | |
| + *			given CPR3 hardware thread
 | |
| + * @of_node:		Device node associated with the device tree child node
 | |
| + *			of this CPR3 regulator
 | |
| + * @thread:		Pointer to the CPR3 thread which manages this CPR3
 | |
| + *			regulator
 | |
| + * @name:		Unique name for this CPR3 regulator which is filled
 | |
| + *			using the device tree regulator-name property
 | |
| + * @rdesc:		Regulator description for this CPR3 regulator
 | |
| + * @rdev:		Regulator device pointer for the regulator registered
 | |
| + *			for this CPR3 regulator
 | |
| + * @mem_acc_regulator:	Pointer to the optional mem-acc supply regulator used
 | |
| + *			to manage memory circuitry settings based upon CPR3
 | |
| + *			regulator output voltage.
 | |
| + * @corner:		Array of all corners supported by this CPR3 regulator
 | |
| + * @corner_count:	The number of elements in the corner array
 | |
| + * @corner_band:	Array of all corner bands supported by CPRh compatible
 | |
| + *			controllers
 | |
| + * @cpr4_regulator_data Target specific cpr4 regulator data
 | |
| + * @cpr3_regulator_data Target specific cpr3 regulator data
 | |
| + * @corner_band_count:	The number of elements in the corner band array
 | |
| + * @platform_fuses:	Pointer to platform specific CPR fuse data (only used by
 | |
| + *			platform specific CPR3 driver)
 | |
| + * @speed_bin_fuse:	Value read from the speed bin fuse parameter
 | |
| + * @speed_bins_supported: The number of speed bins supported by the device tree
 | |
| + *			configuration for this CPR3 regulator
 | |
| + * @cpr_rev_fuse:	Value read from the CPR fusing revision fuse parameter
 | |
| + * @fuse_combo:		Platform specific enum value identifying the specific
 | |
| + *			combination of fuse values found on a given chip
 | |
| + * @fuse_combos_supported: The number of fuse combinations supported by the
 | |
| + *			device tree configuration for this CPR3 regulator
 | |
| + * @fuse_corner_count:	Number of corners defined by fuse parameters
 | |
| + * @fuse_corner_map:	Array of length fuse_corner_count which specifies the
 | |
| + *			highest corner associated with each fuse corner.  Note
 | |
| + *			that each element must correspond to a valid corner
 | |
| + *			and that element values must be strictly increasing.
 | |
| + *			Also, it is acceptable for the lowest fuse corner to map
 | |
| + *			to a corner other than the lowest.  Likewise, it is
 | |
| + *			acceptable for the highest fuse corner to map to a
 | |
| + *			corner other than the highest.
 | |
| + * @fuse_combo_corner_sum: The sum of the corner counts across all fuse combos
 | |
| + * @fuse_combo_offset:	The device tree property array offset for the selected
 | |
| + *			fuse combo
 | |
| + * @speed_bin_corner_sum: The sum of the corner counts across all speed bins
 | |
| + *			This may be specified as 0 if per speed bin parsing
 | |
| + *			support is not required.
 | |
| + * @speed_bin_offset:	The device tree property array offset for the selected
 | |
| + *			speed bin
 | |
| + * @fuse_combo_corner_band_sum: The sum of the corner band counts across all
 | |
| + *			fuse combos
 | |
| + * @fuse_combo_corner_band_offset: The device tree property array offset for
 | |
| + *			the corner band count corresponding to the selected
 | |
| + *			fuse combo
 | |
| + * @speed_bin_corner_band_sum: The sum of the corner band counts across all
 | |
| + *			speed bins. This may be specified as 0 if per speed bin
 | |
| + *			parsing support is not required
 | |
| + * @speed_bin_corner_band_offset: The device tree property array offset for the
 | |
| + *			corner band count corresponding to the selected speed
 | |
| + *			bin
 | |
| + * @pd_bypass_mask:	Bit mask of power domains associated with this CPR3
 | |
| + *			regulator
 | |
| + * @dynamic_floor_corner: Index identifying the voltage corner for the CPR3
 | |
| + *			regulator whose last_volt value should be used as the
 | |
| + *			global CPR floor voltage if all of the power domains
 | |
| + *			associated with this CPR3 regulator are bypassed
 | |
| + * @uses_dynamic_floor: Boolean flag indicating that dynamic_floor_corner should
 | |
| + *			be utilized for the CPR3 regulator
 | |
| + * @current_corner:	Index identifying the currently selected voltage corner
 | |
| + *			for the CPR3 regulator or less than 0 if no corner has
 | |
| + *			been requested
 | |
| + * @last_closed_loop_corner: Index identifying the last voltage corner for the
 | |
| + *			CPR3 regulator which was configured when operating in
 | |
| + *			CPR closed-loop mode or less than 0 if no corner has
 | |
| + *			been requested.  CPR registers are only written to when
 | |
| + *			using closed-loop mode.
 | |
| + * @aggregated:		Boolean flag indicating that this CPR3 regulator
 | |
| + *			participated in the last aggregation event
 | |
| + * @debug_corner:	Index identifying voltage corner used for displaying
 | |
| + *			corner configuration values in debugfs
 | |
| + * @vreg_enabled:	Boolean defining the enable state of the CPR3
 | |
| + *			regulator's regulator within the regulator framework.
 | |
| + * @aging_allowed:	Boolean defining if CPR aging adjustments are allowed
 | |
| + *			for this CPR3 regulator given the fuse combo of the
 | |
| + *			device
 | |
| + * @aging_allow_open_loop_adj: Boolean defining if the open-loop voltage of each
 | |
| + *			corner of this regulator should be adjusted as a result
 | |
| + *			of an aging measurement.  This flag can be set to false
 | |
| + *			when the open-loop voltage adjustments have been
 | |
| + *			specified such that they include the maximum possible
 | |
| + *			aging adjustment.  This flag is only used if
 | |
| + *			aging_allowed == true.
 | |
| + * @aging_corner:	The corner that should be configured for this regulator
 | |
| + *			when an aging measurement is performed.
 | |
| + * @aging_max_adjust_volt: The maximum aging voltage margin in microvolts that
 | |
| + *			may be added to the target quotients of this regulator.
 | |
| + *			A value of 0 may be specified if this regulator does not
 | |
| + *			require any aging adjustment.
 | |
| + * @allow_core_count_adj: Core count adjustments are allowed for this regulator.
 | |
| + * @allow_temp_adj:	Temperature based adjustments are allowed for this
 | |
| + *			regulator.
 | |
| + * @max_core_count:	Maximum number of cores considered for core count
 | |
| + *			adjustment logic.
 | |
| + * @allow_boost:	Voltage boost allowed for this regulator.
 | |
| + *
 | |
| + * This structure contains both configuration and runtime state data.  The
 | |
| + * elements current_corner, last_closed_loop_corner, aggregated, debug_corner,
 | |
| + * and vreg_enabled are state variables.
 | |
| + */
 | |
| +struct cpr3_regulator {
 | |
| +	struct device_node	*of_node;
 | |
| +	struct cpr3_thread	*thread;
 | |
| +	const char		*name;
 | |
| +	struct regulator_desc	rdesc;
 | |
| +	struct regulator_dev	*rdev;
 | |
| +	struct regulator	*mem_acc_regulator;
 | |
| +	struct cpr3_corner	*corner;
 | |
| +	int			corner_count;
 | |
| +	struct cprh_corner_band *corner_band;
 | |
| +	struct cpr4_reg_data    *cpr4_regulator_data;
 | |
| +	struct cpr3_reg_data    *cpr3_regulator_data;
 | |
| +	u32			corner_band_count;
 | |
| +
 | |
| +	void			*platform_fuses;
 | |
| +	int			speed_bin_fuse;
 | |
| +	int			speed_bins_supported;
 | |
| +	int			cpr_rev_fuse;
 | |
| +	int			part_type;
 | |
| +	int			part_type_supported;
 | |
| +	int			fuse_combo;
 | |
| +	int			fuse_combos_supported;
 | |
| +	int			fuse_corner_count;
 | |
| +	int			*fuse_corner_map;
 | |
| +	int			fuse_combo_corner_sum;
 | |
| +	int			fuse_combo_offset;
 | |
| +	int			speed_bin_corner_sum;
 | |
| +	int			speed_bin_offset;
 | |
| +	int			fuse_combo_corner_band_sum;
 | |
| +	int			fuse_combo_corner_band_offset;
 | |
| +	int			speed_bin_corner_band_sum;
 | |
| +	int			speed_bin_corner_band_offset;
 | |
| +	u32			pd_bypass_mask;
 | |
| +	int			dynamic_floor_corner;
 | |
| +	bool			uses_dynamic_floor;
 | |
| +
 | |
| +	int			current_corner;
 | |
| +	int			last_closed_loop_corner;
 | |
| +	bool			aggregated;
 | |
| +	int			debug_corner;
 | |
| +	bool			vreg_enabled;
 | |
| +
 | |
| +	bool			aging_allowed;
 | |
| +	bool			aging_allow_open_loop_adj;
 | |
| +	int			aging_corner;
 | |
| +	int			aging_max_adjust_volt;
 | |
| +
 | |
| +	bool			allow_core_count_adj;
 | |
| +	bool			allow_temp_adj;
 | |
| +	int			max_core_count;
 | |
| +	bool			allow_boost;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_thread - CPR3 hardware thread data structure
 | |
| + * @thread_id:		Hardware thread ID
 | |
| + * @of_node:		Device node associated with the device tree child node
 | |
| + *			of this CPR3 thread
 | |
| + * @ctrl:		Pointer to the CPR3 controller which manages this thread
 | |
| + * @vreg:		Array of CPR3 regulators handled by the CPR3 thread
 | |
| + * @vreg_count:		Number of elements in the vreg array
 | |
| + * @aggr_corner:	CPR corner containing the in process aggregated voltage
 | |
| + *			and target quotient configurations which will be applied
 | |
| + * @last_closed_loop_aggr_corner: CPR corner containing the most recent
 | |
| + *			configurations which were written into hardware
 | |
| + *			registers when operating in closed loop mode (i.e. with
 | |
| + *			CPR enabled)
 | |
| + * @consecutive_up:	The number of consecutive CPR step up events needed to
 | |
| + *			to trigger an up interrupt
 | |
| + * @consecutive_down:	The number of consecutive CPR step down events needed to
 | |
| + *			to trigger a down interrupt
 | |
| + * @up_threshold:	The number CPR error steps required to generate an up
 | |
| + *			event
 | |
| + * @down_threshold:	The number CPR error steps required to generate a down
 | |
| + *			event
 | |
| + *
 | |
| + * This structure contains both configuration and runtime state data.  The
 | |
| + * elements aggr_corner and last_closed_loop_aggr_corner are state variables.
 | |
| + */
 | |
| +struct cpr3_thread {
 | |
| +	u32			thread_id;
 | |
| +	struct device_node	*of_node;
 | |
| +	struct cpr3_controller	*ctrl;
 | |
| +	struct cpr3_regulator	*vreg;
 | |
| +	int			vreg_count;
 | |
| +	struct cpr3_corner	aggr_corner;
 | |
| +	struct cpr3_corner	last_closed_loop_aggr_corner;
 | |
| +
 | |
| +	u32			consecutive_up;
 | |
| +	u32			consecutive_down;
 | |
| +	u32			up_threshold;
 | |
| +	u32			down_threshold;
 | |
| +};
 | |
| +
 | |
| +/* Per CPR controller data */
 | |
| +/**
 | |
| + * enum cpr3_mem_acc_corners - Constants which define the number of mem-acc
 | |
| + *		regulator corners available in the mem-acc corner map array.
 | |
| + * %CPR3_MEM_ACC_LOW_CORNER:	Index in mem-acc corner map array mapping to the
 | |
| + *				mem-acc regulator corner
 | |
| + *				to be used for low voltage vdd supply
 | |
| + * %CPR3_MEM_ACC_HIGH_CORNER:	Index in mem-acc corner map array mapping to the
 | |
| + *				mem-acc regulator corner to be used for high
 | |
| + *				voltage vdd supply
 | |
| + * %CPR3_MEM_ACC_CORNERS:	Number of elements in the mem-acc corner map
 | |
| + *				array
 | |
| + */
 | |
| +enum cpr3_mem_acc_corners {
 | |
| +	CPR3_MEM_ACC_LOW_CORNER		= 0,
 | |
| +	CPR3_MEM_ACC_HIGH_CORNER	= 1,
 | |
| +	CPR3_MEM_ACC_CORNERS		= 2,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * enum cpr3_count_mode - CPR3 controller count mode which defines the
 | |
| + *		method that CPR sensor data is acquired
 | |
| + * %CPR3_COUNT_MODE_ALL_AT_ONCE_MIN:	Capture all CPR sensor readings
 | |
| + *					simultaneously and report the minimum
 | |
| + *					value seen in successive measurements
 | |
| + * %CPR3_COUNT_MODE_ALL_AT_ONCE_MAX:	Capture all CPR sensor readings
 | |
| + *					simultaneously and report the maximum
 | |
| + *					value seen in successive measurements
 | |
| + * %CPR3_COUNT_MODE_STAGGERED:		Read one sensor at a time in a
 | |
| + *					sequential fashion
 | |
| + * %CPR3_COUNT_MODE_ALL_AT_ONCE_AGE:	Capture all CPR aging sensor readings
 | |
| + *					simultaneously.
 | |
| + */
 | |
| +enum cpr3_count_mode {
 | |
| +	CPR3_COUNT_MODE_ALL_AT_ONCE_MIN	= 0,
 | |
| +	CPR3_COUNT_MODE_ALL_AT_ONCE_MAX	= 1,
 | |
| +	CPR3_COUNT_MODE_STAGGERED	= 2,
 | |
| +	CPR3_COUNT_MODE_ALL_AT_ONCE_AGE	= 3,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * enum cpr_controller_type - supported CPR controller hardware types
 | |
| + * %CPR_CTRL_TYPE_CPR3:	HW has CPR3 controller
 | |
| + * %CPR_CTRL_TYPE_CPR4:	HW has CPR4 controller
 | |
| + */
 | |
| +enum cpr_controller_type {
 | |
| +	CPR_CTRL_TYPE_CPR3,
 | |
| +	CPR_CTRL_TYPE_CPR4,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * cpr_setting - supported CPR global settings
 | |
| + * %CPR_DEFAULT: default mode from dts will be used
 | |
| + * %CPR_DISABLED: ceiling voltage will be used for all the corners
 | |
| + * %CPR_OPEN_LOOP_EN: CPR will work in OL
 | |
| + * %CPR_CLOSED_LOOP_EN: CPR will work in CL, if supported
 | |
| + */
 | |
| +enum cpr_setting {
 | |
| +	CPR_DEFAULT		= 0,
 | |
| +	CPR_DISABLED		= 1,
 | |
| +	CPR_OPEN_LOOP_EN	= 2,
 | |
| +	CPR_CLOSED_LOOP_EN	= 3,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_aging_sensor_info - CPR3 aging sensor information
 | |
| + * @sensor_id		The index of the CPR3 sensor to be used in the aging
 | |
| + *			measurement.
 | |
| + * @ro_scale		The CPR ring oscillator (RO) scaling factor for the
 | |
| + *			aging sensor with units of QUOT/V.
 | |
| + * @init_quot_diff:	The fused quotient difference between aged and un-aged
 | |
| + *			paths that was measured at manufacturing time.
 | |
| + * @measured_quot_diff: The quotient difference measured at runtime.
 | |
| + * @bypass_mask:	Bit mask of the CPR sensors that must be bypassed during
 | |
| + *			the aging measurement for this sensor
 | |
| + *
 | |
| + * This structure contains both configuration and runtime state data.  The
 | |
| + * element measured_quot_diff is a state variable.
 | |
| + */
 | |
| +struct cpr3_aging_sensor_info {
 | |
| +	u32			sensor_id;
 | |
| +	u32			ro_scale;
 | |
| +	int			init_quot_diff;
 | |
| +	int			measured_quot_diff;
 | |
| +	u32			bypass_mask[CPR3_MAX_SENSOR_COUNT / 32];
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_reg_info - Register information data structure
 | |
| + * @name:	Register name
 | |
| + * @addr:	Register physical address
 | |
| + * @value:	Register content
 | |
| + * @virt_addr:	Register virtual address
 | |
| + *
 | |
| + * This data structure is used to dump some critical register contents
 | |
| + * when the device crashes due to a kernel panic.
 | |
| + */
 | |
| +struct cpr3_reg_info {
 | |
| +	const char	*name;
 | |
| +	u32		addr;
 | |
| +	u32		value;
 | |
| +	void __iomem	*virt_addr;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_panic_regs_info - Data structure to dump critical register
 | |
| + *		contents.
 | |
| + * @reg_count:		Number of elements in the regs array
 | |
| + * @regs:		Array of critical registers information
 | |
| + *
 | |
| + * This data structure is used to dump critical register contents when
 | |
| + * the device crashes due to a kernel panic.
 | |
| + */
 | |
| +struct cpr3_panic_regs_info {
 | |
| +	int			reg_count;
 | |
| +	struct cpr3_reg_info	*regs;
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * struct cpr3_controller - CPR3 controller data structure
 | |
| + * @dev:		Device pointer for the CPR3 controller device
 | |
| + * @name:		Unique name for the CPR3 controller
 | |
| + * @ctrl_id:		Controller ID corresponding to the VDD supply number
 | |
| + *			that this CPR3 controller manages.
 | |
| + * @cpr_ctrl_base:	Virtual address of the CPR3 controller base register
 | |
| + * @fuse_base:		Virtual address of fuse row 0
 | |
| + * @aging_possible_reg:	Virtual address of an optional platform-specific
 | |
| + *			register that must be ready to determine if it is
 | |
| + *			possible to perform an aging measurement.
 | |
| + * @list:		list head used in a global cpr3-regulator list so that
 | |
| + *			cpr3-regulator structs can be found easily in RAM dumps
 | |
| + * @thread:		Array of CPR3 threads managed by the CPR3 controller
 | |
| + * @thread_count:	Number of elements in the thread array
 | |
| + * @sensor_owner:	Array of thread IDs indicating which thread owns a given
 | |
| + *			CPR sensor
 | |
| + * @sensor_count:	The number of CPR sensors found on the CPR loop managed
 | |
| + *			by this CPR controller.  Must be equal to the number of
 | |
| + *			elements in the sensor_owner array
 | |
| + * @soc_revision:	Revision number of the SoC.  This may be unused by
 | |
| + *			platforms that do not have different behavior for
 | |
| + *			different SoC revisions.
 | |
| + * @lock:		Mutex lock used to ensure mutual exclusion between
 | |
| + *			all of the threads associated with the controller
 | |
| + * @vdd_regulator:	Pointer to the VDD supply regulator which this CPR3
 | |
| + *			controller manages
 | |
| + * @system_regulator:	Pointer to the optional system-supply regulator upon
 | |
| + *			which the VDD supply regulator depends.
 | |
| + * @mem_acc_regulator:	Pointer to the optional mem-acc supply regulator used
 | |
| + *			to manage memory circuitry settings based upon the
 | |
| + *			VDD supply output voltage.
 | |
| + * @vdd_limit_regulator: Pointer to the VDD supply limit regulator which is used
 | |
| + *			for hardware closed-loop in order specify ceiling and
 | |
| + *			floor voltage limits (platform specific)
 | |
| + * @system_supply_max_volt: Voltage in microvolts which corresponds to the
 | |
| + *			absolute ceiling voltage of the system-supply
 | |
| + * @mem_acc_threshold_volt: mem-acc threshold voltage in microvolts
 | |
| + * @mem_acc_corner_map: mem-acc regulator corners mapping to low and high
 | |
| + *			voltage mem-acc settings for the memories powered by
 | |
| + *			this CPR3 controller and its associated CPR3 regulators
 | |
| + * @mem_acc_crossover_volt: Voltage in microvolts corresponding to the voltage
 | |
| + *			that the VDD supply must be set to while a MEM ACC
 | |
| + *			switch is in progress. This element must be initialized
 | |
| + *			for CPRh controllers when a MEM ACC threshold voltage is
 | |
| + *			defined.
 | |
| + * @core_clk:		Pointer to the CPR3 controller core clock
 | |
| + * @iface_clk:		Pointer to the CPR3 interface clock (platform specific)
 | |
| + * @bus_clk:		Pointer to the CPR3 bus clock (platform specific)
 | |
| + * @irq:		CPR interrupt number
 | |
| + * @irq_affinity_mask:	The cpumask for the CPUs which the CPR interrupt should
 | |
| + *			have affinity for
 | |
| + * @cpu_hotplug_notifier: CPU hotplug notifier used to reset IRQ affinity when a
 | |
| + *			CPU is brought back online
 | |
| + * @ceiling_irq:	Interrupt number for the interrupt that is triggered
 | |
| + *			when hardware closed-loop attempts to exceed the ceiling
 | |
| + *			voltage
 | |
| + * @apm:		Handle to the array power mux (APM)
 | |
| + * @apm_threshold_volt:	Voltage in microvolts which defines the threshold
 | |
| + *			voltage to determine the APM supply selection for
 | |
| + *			each corner
 | |
| + * @apm_crossover_volt:	Voltage in microvolts corresponding to the voltage that
 | |
| + *			the VDD supply must be set to while an APM switch is in
 | |
| + *			progress. This element must be initialized for CPRh
 | |
| + *			controllers when an APM threshold voltage is defined
 | |
| + * @apm_adj_volt:	Minimum difference between APM threshold voltage and
 | |
| + *			open-loop voltage which allows the APM threshold voltage
 | |
| + *			to be used as a ceiling
 | |
| + * @apm_high_supply:	APM supply to configure if VDD voltage is greater than
 | |
| + *			or equal to the APM threshold voltage
 | |
| + * @apm_low_supply:	APM supply to configure if the VDD voltage is less than
 | |
| + *			the APM threshold voltage
 | |
| + * @base_volt:		Minimum voltage in microvolts supported by the VDD
 | |
| + *			supply managed by this CPR controller
 | |
| + * @corner_switch_delay_time: The delay time in nanoseconds used by the CPR
 | |
| + *			controller to wait for voltage settling before
 | |
| + *			acknowledging the OSM block after corner changes
 | |
| + * @cpr_clock_rate:	CPR reference clock frequency in Hz.
 | |
| + * @sensor_time:	The time in nanoseconds that each sensor takes to
 | |
| + *			perform a measurement.
 | |
| + * @loop_time:		The time in nanoseconds between consecutive CPR
 | |
| + *			measurements.
 | |
| + * @up_down_delay_time: The time to delay in nanoseconds between consecutive CPR
 | |
| + *			measurements when the last measurement recommended
 | |
| + *			increasing or decreasing the vdd-supply voltage.
 | |
| + *			(platform specific)
 | |
| + * @idle_clocks:	Number of CPR reference clock ticks that the CPR
 | |
| + *			controller waits in transitional states.
 | |
| + * @step_quot_init_min:	The default minimum CPR step quotient value.  The step
 | |
| + *			quotient is the number of additional ring oscillator
 | |
| + *			ticks observed when increasing one step in vdd-supply
 | |
| + *			output voltage.
 | |
| + * @step_quot_init_max:	The default maximum CPR step quotient value.
 | |
| + * @step_volt:		Step size in microvolts between available set points
 | |
| + *			of the VDD supply
 | |
| + * @down_error_step_limit: CPR4 hardware closed-loop down error step limit which
 | |
| + *			defines the maximum number of VDD supply regulator steps
 | |
| + *			that the voltage may be reduced as the result of a
 | |
| + *			single CPR measurement.
 | |
| + * @up_error_step_limit: CPR4 hardware closed-loop up error step limit which
 | |
| + *			defines the maximum number of VDD supply regulator steps
 | |
| + *			that the voltage may be increased as the result of a
 | |
| + *			single CPR measurement.
 | |
| + * @count_mode:		CPR controller count mode
 | |
| + * @count_repeat:	Number of times to perform consecutive sensor
 | |
| + *			measurements when using all-at-once count modes.
 | |
| + * @proc_clock_throttle: Defines the processor clock frequency throttling
 | |
| + *			register value to use.  This can be used to reduce the
 | |
| + *			clock frequency when a power domain exits a low power
 | |
| + *			mode until CPR settles at a new voltage.
 | |
| + *			(platform specific)
 | |
| + * @cpr_allowed_hw:	Boolean which indicates if closed-loop CPR operation is
 | |
| + *			permitted for a given chip based upon hardware fuse
 | |
| + *			values
 | |
| + * @cpr_allowed_sw:	Boolean which indicates if closed-loop CPR operation is
 | |
| + *			permitted based upon software policies
 | |
| + * @supports_hw_closed_loop: Boolean which indicates if this CPR3/4 controller
 | |
| + *			physically supports hardware closed-loop CPR operation
 | |
| + * @use_hw_closed_loop:	Boolean which indicates that this controller will be
 | |
| + *			using hardware closed-loop operation in place of
 | |
| + *			software closed-loop operation.
 | |
| + * @ctrl_type:		CPR controller type
 | |
| + * @saw_use_unit_mV:	Boolean which indicates the unit used in SAW PVC
 | |
| + *			interface is mV.
 | |
| + * @aggr_corner:	CPR corner containing the most recently aggregated
 | |
| + *			voltage configurations which are being used currently
 | |
| + * @cpr_enabled:	Boolean which indicates that the CPR controller is
 | |
| + *			enabled and operating in closed-loop mode.  CPR clocks
 | |
| + *			have been prepared and enabled whenever this flag is
 | |
| + *			true.
 | |
| + * @last_corner_was_closed_loop: Boolean indicating if the last known corners
 | |
| + *			were updated during closed loop operation.
 | |
| + * @cpr_suspended:	Boolean which indicates that CPR has been temporarily
 | |
| + *			disabled while enterring system suspend.
 | |
| + * @debugfs:		Pointer to the debugfs directory of this CPR3 controller
 | |
| + * @aging_ref_volt:	Reference voltage in microvolts to configure when
 | |
| + *			performing CPR aging measurements.
 | |
| + * @aging_vdd_mode:	vdd-supply regulator mode to configure before performing
 | |
| + *			a CPR aging measurement.  It should be one of
 | |
| + *			REGULATOR_MODE_*.
 | |
| + * @aging_complete_vdd_mode: vdd-supply regulator mode to configure after
 | |
| + *			performing a CPR aging measurement.  It should be one of
 | |
| + *			REGULATOR_MODE_*.
 | |
| + * @aging_ref_adjust_volt: The reference aging voltage margin in microvolts that
 | |
| + *			should be added to the target quotients of the
 | |
| + *			regulators managed by this controller after derating.
 | |
| + * @aging_required:	Flag which indicates that a CPR aging measurement still
 | |
| + *			needs to be performed for this CPR3 controller.
 | |
| + * @aging_succeeded:	Flag which indicates that a CPR aging measurement has
 | |
| + *			completed successfully.
 | |
| + * @aging_failed:	Flag which indicates that a CPR aging measurement has
 | |
| + *			failed to complete successfully.
 | |
| + * @aging_sensor:	Array of CPR3 aging sensors which are used to perform
 | |
| + *			aging measurements at a runtime.
 | |
| + * @aging_sensor_count:	Number of elements in the aging_sensor array
 | |
| + * @aging_possible_mask: Optional bitmask used to mask off the
 | |
| + *			aging_possible_reg register.
 | |
| + * @aging_possible_val:	Optional value that the masked aging_possible_reg
 | |
| + *			register must have in order for a CPR aging measurement
 | |
| + *			to be possible.
 | |
| + * @step_quot_fixed:	Fixed step quotient value used for target quotient
 | |
| + *			adjustment if use_dynamic_step_quot is not set.
 | |
| + *			This parameter is only relevant for CPR4 controllers
 | |
| + *			when using the per-online-core or per-temperature
 | |
| + *			adjustments.
 | |
| + * @initial_temp_band:	Temperature band used for calculation of base-line
 | |
| + *			target quotients (fused).
 | |
| + * @use_dynamic_step_quot: Boolean value which indicates that margin adjustment
 | |
| + *			of target quotient will be based on the step quotient
 | |
| + *			calculated dynamically in hardware for each RO.
 | |
| + * @allow_core_count_adj: Core count adjustments are allowed for this controller
 | |
| + * @allow_temp_adj:	Temperature based adjustments are allowed for
 | |
| + *			this controller
 | |
| + * @allow_boost:	Voltage boost allowed for this controller.
 | |
| + * @temp_band_count:	Number of temperature bands used for temperature based
 | |
| + *			adjustment logic
 | |
| + * @temp_points:	Array of temperature points in decidegrees Celsius used
 | |
| + *			to specify the ranges for selected temperature bands.
 | |
| + *			The array must have (temp_band_count - 1) elements
 | |
| + *			allocated.
 | |
| + * @temp_sensor_id_start: Start ID of temperature sensors used for temperature
 | |
| + *			based adjustments.
 | |
| + * @temp_sensor_id_end:	End ID of temperature sensors used for temperature
 | |
| + *			based adjustments.
 | |
| + * @voltage_settling_time: The time in nanoseconds that it takes for the
 | |
| + *			VDD supply voltage to settle after being increased or
 | |
| + *			decreased by step_volt microvolts which is used when
 | |
| + *			SDELTA voltage margin adjustments are applied.
 | |
| + * @cpr_global_setting:	Global setting for this CPR controller
 | |
| + * @panic_regs_info:	Array of panic registers information which provides the
 | |
| + *			list of registers to dump when the device crashes.
 | |
| + * @panic_notifier:	Notifier block registered to global panic notifier list.
 | |
| + *
 | |
| + * This structure contains both configuration and runtime state data.  The
 | |
| + * elements cpr_allowed_sw, use_hw_closed_loop, aggr_corner, cpr_enabled,
 | |
| + * last_corner_was_closed_loop, cpr_suspended, aging_ref_adjust_volt,
 | |
| + * aging_required, aging_succeeded, and aging_failed are state variables.
 | |
| + *
 | |
| + * The apm* elements do not need to be initialized if the VDD supply managed by
 | |
| + * the CPR3 controller does not utilize an APM.
 | |
| + *
 | |
| + * The elements step_quot_fixed, initial_temp_band, allow_core_count_adj,
 | |
| + * allow_temp_adj and temp* need to be initialized for CPR4 controllers which
 | |
| + * are using per-online-core or per-temperature adjustments.
 | |
| + */
 | |
| +struct cpr3_controller {
 | |
| +	struct device		*dev;
 | |
| +	const char		*name;
 | |
| +	int			ctrl_id;
 | |
| +	void __iomem		*cpr_ctrl_base;
 | |
| +	void __iomem		*fuse_base;
 | |
| +	void __iomem		*aging_possible_reg;
 | |
| +	struct list_head	list;
 | |
| +	struct cpr3_thread	*thread;
 | |
| +	int			thread_count;
 | |
| +	u8			*sensor_owner;
 | |
| +	int			sensor_count;
 | |
| +	int			soc_revision;
 | |
| +	struct mutex		lock;
 | |
| +	struct regulator	*vdd_regulator;
 | |
| +	struct regulator	*system_regulator;
 | |
| +	struct regulator	*mem_acc_regulator;
 | |
| +	struct regulator	*vdd_limit_regulator;
 | |
| +	int			system_supply_max_volt;
 | |
| +	int			mem_acc_threshold_volt;
 | |
| +	int			mem_acc_corner_map[CPR3_MEM_ACC_CORNERS];
 | |
| +	int			mem_acc_crossover_volt;
 | |
| +	struct clk		*core_clk;
 | |
| +	struct clk		*iface_clk;
 | |
| +	struct clk		*bus_clk;
 | |
| +	int			irq;
 | |
| +	struct cpumask		irq_affinity_mask;
 | |
| +	struct notifier_block	cpu_hotplug_notifier;
 | |
| +	int			ceiling_irq;
 | |
| +	struct msm_apm_ctrl_dev *apm;
 | |
| +	int			apm_threshold_volt;
 | |
| +	int			apm_crossover_volt;
 | |
| +	int			apm_adj_volt;
 | |
| +	enum msm_apm_supply	apm_high_supply;
 | |
| +	enum msm_apm_supply	apm_low_supply;
 | |
| +	int			base_volt;
 | |
| +	u32			corner_switch_delay_time;
 | |
| +	u32			cpr_clock_rate;
 | |
| +	u32			sensor_time;
 | |
| +	u32			loop_time;
 | |
| +	u32			up_down_delay_time;
 | |
| +	u32			idle_clocks;
 | |
| +	u32			step_quot_init_min;
 | |
| +	u32			step_quot_init_max;
 | |
| +	int			step_volt;
 | |
| +	u32			down_error_step_limit;
 | |
| +	u32			up_error_step_limit;
 | |
| +	enum cpr3_count_mode	count_mode;
 | |
| +	u32			count_repeat;
 | |
| +	u32			proc_clock_throttle;
 | |
| +	bool			cpr_allowed_hw;
 | |
| +	bool			cpr_allowed_sw;
 | |
| +	bool			supports_hw_closed_loop;
 | |
| +	bool			use_hw_closed_loop;
 | |
| +	enum cpr_controller_type ctrl_type;
 | |
| +	bool			saw_use_unit_mV;
 | |
| +	struct cpr3_corner	aggr_corner;
 | |
| +	bool			cpr_enabled;
 | |
| +	bool			last_corner_was_closed_loop;
 | |
| +	bool			cpr_suspended;
 | |
| +	struct dentry		*debugfs;
 | |
| +
 | |
| +	int			aging_ref_volt;
 | |
| +	unsigned int		aging_vdd_mode;
 | |
| +	unsigned int		aging_complete_vdd_mode;
 | |
| +	int			aging_ref_adjust_volt;
 | |
| +	bool			aging_required;
 | |
| +	bool			aging_succeeded;
 | |
| +	bool			aging_failed;
 | |
| +	struct cpr3_aging_sensor_info *aging_sensor;
 | |
| +	int			aging_sensor_count;
 | |
| +	u32			cur_sensor_state;
 | |
| +	u32			aging_possible_mask;
 | |
| +	u32			aging_possible_val;
 | |
| +
 | |
| +	u32			step_quot_fixed;
 | |
| +	u32			initial_temp_band;
 | |
| +	bool			use_dynamic_step_quot;
 | |
| +	bool			allow_core_count_adj;
 | |
| +	bool			allow_temp_adj;
 | |
| +	bool			allow_boost;
 | |
| +	int			temp_band_count;
 | |
| +	int			*temp_points;
 | |
| +	u32			temp_sensor_id_start;
 | |
| +	u32			temp_sensor_id_end;
 | |
| +	u32			voltage_settling_time;
 | |
| +	enum cpr_setting	cpr_global_setting;
 | |
| +	struct cpr3_panic_regs_info *panic_regs_info;
 | |
| +	struct notifier_block	panic_notifier;
 | |
| +};
 | |
| +
 | |
| +/* Used for rounding voltages to the closest physically available set point. */
 | |
| +#define CPR3_ROUND(n, d) (DIV_ROUND_UP(n, d) * (d))
 | |
| +
 | |
| +#define cpr3_err(cpr3_thread, message, ...) \
 | |
| +	pr_err("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
 | |
| +#define cpr3_info(cpr3_thread, message, ...) \
 | |
| +	pr_info("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
 | |
| +#define cpr3_debug(cpr3_thread, message, ...) \
 | |
| +	pr_debug("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
 | |
| +
 | |
| +/*
 | |
| + * Offset subtracted from voltage corner values passed in from the regulator
 | |
| + * framework in order to get internal voltage corner values.  This is needed
 | |
| + * since the regulator framework treats 0 as an error value at regulator
 | |
| + * registration time.
 | |
| + */
 | |
| +#define CPR3_CORNER_OFFSET	1
 | |
| +
 | |
| +#ifdef CONFIG_REGULATOR_CPR3
 | |
| +
 | |
| +int cpr3_regulator_register(struct platform_device *pdev,
 | |
| +			struct cpr3_controller *ctrl);
 | |
| +int cpr3_open_loop_regulator_register(struct platform_device *pdev,
 | |
| +				      struct cpr3_controller *ctrl);
 | |
| +int cpr3_regulator_unregister(struct cpr3_controller *ctrl);
 | |
| +int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl);
 | |
| +int cpr3_regulator_suspend(struct cpr3_controller *ctrl);
 | |
| +int cpr3_regulator_resume(struct cpr3_controller *ctrl);
 | |
| +
 | |
| +int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
 | |
| +			u32 max_thread_id);
 | |
| +int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
 | |
| +			struct platform_device *pdev);
 | |
| +int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
 | |
| +			   struct platform_device *pdev, u8 start, u8 end);
 | |
| +int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
 | |
| +			const struct cpr3_fuse_param *param, u64 *param_value);
 | |
| +int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
 | |
| +			int fuse_len);
 | |
| +u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x);
 | |
| +int cpr3_parse_array_property(struct cpr3_regulator *vreg,
 | |
| +			const char *prop_name, int tuple_size, u32 *out);
 | |
| +int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
 | |
| +			const char *prop_name, int tuple_size, u32 *out);
 | |
| +int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
 | |
| +			const char *prop_name, int tuple_size, u32 *out);
 | |
| +int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg);
 | |
| +int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
 | |
| +			u32 *out_value, u32 value_min, u32 value_max);
 | |
| +int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
 | |
| +			u32 *out_value, u32 value_min, u32 value_max);
 | |
| +int cpr3_parse_common_thread_data(struct cpr3_thread *thread);
 | |
| +int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl);
 | |
| +int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl);
 | |
| +int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg);
 | |
| +void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg);
 | |
| +int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg);
 | |
| +void cpr3_print_quots(struct cpr3_regulator *vreg);
 | |
| +int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt);
 | |
| +int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt);
 | |
| +int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt);
 | |
| +int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg);
 | |
| +int cpr3_quot_adjustment(int ro_scale, int volt_adjust);
 | |
| +int cpr3_voltage_adjustment(int ro_scale, int quot_adjust);
 | |
| +int cpr3_parse_closed_loop_voltage_adjustments(struct cpr3_regulator *vreg,
 | |
| +			u64 *ro_sel, int *volt_adjust,
 | |
| +			int *volt_adjust_fuse, int *ro_scale);
 | |
| +int cpr4_parse_core_count_temp_voltage_adj(struct cpr3_regulator *vreg,
 | |
| +			bool use_corner_band);
 | |
| +int cpr3_apm_init(struct cpr3_controller *ctrl);
 | |
| +int cpr3_mem_acc_init(struct cpr3_regulator *vreg);
 | |
| +void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg);
 | |
| +void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg);
 | |
| +int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt_adjust);
 | |
| +int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
 | |
| +			bool is_cold);
 | |
| +int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp);
 | |
| +bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg);
 | |
| +
 | |
| +#else
 | |
| +
 | |
| +static inline int cpr3_regulator_register(struct platform_device *pdev,
 | |
| +			struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_open_loop_regulator_register(struct platform_device *pdev,
 | |
| +				  struct cpr3_controller *ctrl);
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_regulator_resume(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_get_thread_name(struct cpr3_thread *thread,
 | |
| +			struct device_node *thread_node)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_allocate_threads(struct cpr3_controller *ctrl,
 | |
| +			u32 min_thread_id, u32 max_thread_id)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
 | |
| +			struct platform_device *pdev)
 | |
| +{
 | |
| +	return -ENXIO;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
 | |
| +			   struct platform_device *pdev, u8 start, u8 end)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
 | |
| +			const struct cpr3_fuse_param *param, u64 *param_value)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_convert_open_loop_voltage_fuse(int ref_volt,
 | |
| +			int step_volt, u32 fuse, int fuse_len)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_array_property(struct cpr3_regulator *vreg,
 | |
| +			const char *prop_name, int tuple_size, u32 *out)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
 | |
| +			const char *prop_name, int tuple_size, u32 *out)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_corner_band_array_property(
 | |
| +			struct cpr3_regulator *vreg, const char *prop_name,
 | |
| +			int tuple_size, u32 *out)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_thread_u32(struct cpr3_thread *thread,
 | |
| +			const char *propname, u32 *out_value, u32 value_min,
 | |
| +			u32 value_max)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl,
 | |
| +			const char *propname, u32 *out_value, u32 value_min,
 | |
| +			u32 value_max)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline void cpr3_open_loop_voltage_as_ceiling(
 | |
| +			struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline void cpr3_print_quots(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_adjust_fused_open_loop_voltages(
 | |
| +			struct cpr3_regulator *vreg, int *fuse_volt)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return -EPERM;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_parse_closed_loop_voltage_adjustments(
 | |
| +			struct cpr3_regulator *vreg, u64 *ro_sel,
 | |
| +			int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr4_parse_core_count_temp_voltage_adj(
 | |
| +			struct cpr3_regulator *vreg, bool use_corner_band)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_apm_init(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +}
 | |
| +
 | |
| +static inline void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +}
 | |
| +
 | |
| +static inline int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt_adjust)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
 | |
| +			bool is_cold)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static inline bool
 | |
| +cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	return false;
 | |
| +}
 | |
| +
 | |
| +static inline int
 | |
| +cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
 | |
| +{
 | |
| +	return 0;
 | |
| +}
 | |
| +#endif /* CONFIG_REGULATOR_CPR3 */
 | |
| +
 | |
| +#endif /* __REGULATOR_CPR_REGULATOR_H__ */
 | |
| --- /dev/null
 | |
| +++ b/drivers/regulator/cpr3-util.c
 | |
| @@ -0,0 +1,2750 @@
 | |
| +/*
 | |
| + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 and
 | |
| + * only version 2 as published by the Free Software Foundation.
 | |
| + *
 | |
| + * This program is distributed in the hope that it will be useful,
 | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| + * GNU General Public License for more details.
 | |
| + */
 | |
| +
 | |
| +/*
 | |
| + * This file contains utility functions to be used by platform specific CPR3
 | |
| + * regulator drivers.
 | |
| + */
 | |
| +
 | |
| +#define pr_fmt(fmt) "%s: " fmt, __func__
 | |
| +
 | |
| +#include <linux/cpumask.h>
 | |
| +#include <linux/device.h>
 | |
| +#include <linux/io.h>
 | |
| +#include <linux/kernel.h>
 | |
| +#include <linux/of.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/types.h>
 | |
| +
 | |
| +#include <soc/qcom/socinfo.h>
 | |
| +
 | |
| +#include "cpr3-regulator.h"
 | |
| +
 | |
| +#define BYTES_PER_FUSE_ROW		8
 | |
| +#define MAX_FUSE_ROW_BIT		63
 | |
| +
 | |
| +#define CPR3_CONSECUTIVE_UP_DOWN_MIN	0
 | |
| +#define CPR3_CONSECUTIVE_UP_DOWN_MAX	15
 | |
| +#define CPR3_UP_DOWN_THRESHOLD_MIN	0
 | |
| +#define CPR3_UP_DOWN_THRESHOLD_MAX	31
 | |
| +#define CPR3_STEP_QUOT_MIN		0
 | |
| +#define CPR3_STEP_QUOT_MAX		63
 | |
| +#define CPR3_IDLE_CLOCKS_MIN		0
 | |
| +#define CPR3_IDLE_CLOCKS_MAX		31
 | |
| +
 | |
| +/* This constant has units of uV/mV so 1000 corresponds to 100%. */
 | |
| +#define CPR3_AGING_DERATE_UNITY		1000
 | |
| +
 | |
| +/**
 | |
| + * cpr3_allocate_regulators() - allocate and initialize CPR3 regulators for a
 | |
| + *		given thread based upon device tree data
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + *
 | |
| + * This function allocates the thread->vreg array based upon the number of
 | |
| + * device tree regulator subnodes.  It also initializes generic elements of each
 | |
| + * regulator struct such as name, of_node, and thread.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_allocate_regulators(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	struct device_node *node;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	thread->vreg_count = 0;
 | |
| +
 | |
| +	for_each_available_child_of_node(thread->of_node, node) {
 | |
| +		thread->vreg_count++;
 | |
| +	}
 | |
| +
 | |
| +	thread->vreg = devm_kcalloc(thread->ctrl->dev, thread->vreg_count,
 | |
| +			sizeof(*thread->vreg), GFP_KERNEL);
 | |
| +	if (!thread->vreg)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	i = 0;
 | |
| +	for_each_available_child_of_node(thread->of_node, node) {
 | |
| +		thread->vreg[i].of_node = node;
 | |
| +		thread->vreg[i].thread = thread;
 | |
| +
 | |
| +		rc = of_property_read_string(node, "regulator-name",
 | |
| +						&thread->vreg[i].name);
 | |
| +		if (rc) {
 | |
| +			dev_err(thread->ctrl->dev, "could not find regulator name, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		i++;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_allocate_threads() - allocate and initialize CPR3 threads for a given
 | |
| + *			     controller based upon device tree data
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + * @min_thread_id:	Minimum allowed hardware thread ID for this controller
 | |
| + * @max_thread_id:	Maximum allowed hardware thread ID for this controller
 | |
| + *
 | |
| + * This function allocates the ctrl->thread array based upon the number of
 | |
| + * device tree thread subnodes.  It also initializes generic elements of each
 | |
| + * thread struct such as thread_id, of_node, ctrl, and vreg array.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
 | |
| +			u32 max_thread_id)
 | |
| +{
 | |
| +	struct device *dev = ctrl->dev;
 | |
| +	struct device_node *thread_node;
 | |
| +	int i, j, rc;
 | |
| +
 | |
| +	ctrl->thread_count = 0;
 | |
| +
 | |
| +	for_each_available_child_of_node(dev->of_node, thread_node) {
 | |
| +		ctrl->thread_count++;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->thread = devm_kcalloc(dev, ctrl->thread_count,
 | |
| +			sizeof(*ctrl->thread), GFP_KERNEL);
 | |
| +	if (!ctrl->thread)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	i = 0;
 | |
| +	for_each_available_child_of_node(dev->of_node, thread_node) {
 | |
| +		ctrl->thread[i].of_node = thread_node;
 | |
| +		ctrl->thread[i].ctrl = ctrl;
 | |
| +
 | |
| +		rc = of_property_read_u32(thread_node, "qcom,cpr-thread-id",
 | |
| +					  &ctrl->thread[i].thread_id);
 | |
| +		if (rc) {
 | |
| +			dev_err(dev, "could not read DT property qcom,cpr-thread-id, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		if (ctrl->thread[i].thread_id < min_thread_id ||
 | |
| +				ctrl->thread[i].thread_id > max_thread_id) {
 | |
| +			dev_err(dev, "invalid thread id = %u; not within [%u, %u]\n",
 | |
| +				ctrl->thread[i].thread_id, min_thread_id,
 | |
| +				max_thread_id);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		/* Verify that the thread ID is unique for all child nodes. */
 | |
| +		for (j = 0; j < i; j++) {
 | |
| +			if (ctrl->thread[j].thread_id
 | |
| +					== ctrl->thread[i].thread_id) {
 | |
| +				dev_err(dev, "duplicate thread id = %u found\n",
 | |
| +					ctrl->thread[i].thread_id);
 | |
| +				return -EINVAL;
 | |
| +			}
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr3_allocate_regulators(&ctrl->thread[i]);
 | |
| +		if (rc)
 | |
| +			return rc;
 | |
| +
 | |
| +		i++;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_map_fuse_base() - ioremap the base address of the fuse region
 | |
| + * @ctrl:	Pointer to the CPR3 controller
 | |
| + * @pdev:	Platform device pointer for the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
 | |
| +			struct platform_device *pdev)
 | |
| +{
 | |
| +	struct resource *res;
 | |
| +
 | |
| +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fuse_base");
 | |
| +	if (!res || !res->start) {
 | |
| +		dev_err(&pdev->dev, "fuse base address is missing\n");
 | |
| +		return -ENXIO;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->fuse_base = devm_ioremap(&pdev->dev, res->start,
 | |
| +						resource_size(res));
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_read_tcsr_setting - reads the CPR setting bits from TCSR register
 | |
| + * @ctrl:	Pointer to the CPR3 controller
 | |
| + * @pdev:	Platform device pointer for the CPR3 controller
 | |
| + * @start:	start bit in TCSR register
 | |
| + * @end:	end bit in TCSR register
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
 | |
| +			   struct platform_device *pdev, u8 start, u8 end)
 | |
| +{
 | |
| +	struct resource *res;
 | |
| +	void __iomem *tcsr_reg;
 | |
| +	u32 val;
 | |
| +
 | |
| +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
 | |
| +					   "cpr_tcsr_reg");
 | |
| +	if (!res || !res->start)
 | |
| +		return 0;
 | |
| +
 | |
| +	tcsr_reg = ioremap(res->start, resource_size(res));
 | |
| +	if (!tcsr_reg) {
 | |
| +		dev_err(&pdev->dev, "tcsr ioremap failed\n");
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	val = readl_relaxed(tcsr_reg);
 | |
| +	val &= GENMASK(end, start);
 | |
| +	val >>= start;
 | |
| +
 | |
| +	switch (val) {
 | |
| +	case 1:
 | |
| +		ctrl->cpr_global_setting = CPR_DISABLED;
 | |
| +		break;
 | |
| +	case 2:
 | |
| +		ctrl->cpr_global_setting = CPR_OPEN_LOOP_EN;
 | |
| +		break;
 | |
| +	case 3:
 | |
| +		ctrl->cpr_global_setting = CPR_CLOSED_LOOP_EN;
 | |
| +		break;
 | |
| +	default:
 | |
| +		ctrl->cpr_global_setting = CPR_DEFAULT;
 | |
| +	}
 | |
| +
 | |
| +	iounmap(tcsr_reg);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_read_fuse_param() - reads a CPR3 fuse parameter out of eFuses
 | |
| + * @fuse_base_addr:	Virtual memory address of the eFuse base address
 | |
| + * @param:		Null terminated array of fuse param segments to read
 | |
| + *			from
 | |
| + * @param_value:	Output with value read from the eFuses
 | |
| + *
 | |
| + * This function reads from each of the parameter segments listed in the param
 | |
| + * array and concatenates their values together.  Reading stops when an element
 | |
| + * is reached which has all 0 struct values.  The total number of bits specified
 | |
| + * for the fuse parameter across all segments must be less than or equal to 64.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
 | |
| +		const struct cpr3_fuse_param *param, u64 *param_value)
 | |
| +{
 | |
| +	u64 fuse_val, val;
 | |
| +	int bits;
 | |
| +	int bits_total = 0;
 | |
| +
 | |
| +	*param_value = 0;
 | |
| +
 | |
| +	while (param->row || param->bit_start || param->bit_end) {
 | |
| +		if (param->bit_start > param->bit_end
 | |
| +		    || param->bit_end > MAX_FUSE_ROW_BIT) {
 | |
| +			pr_err("Invalid fuse parameter segment: row=%u, start=%u, end=%u\n",
 | |
| +				param->row, param->bit_start, param->bit_end);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		bits = param->bit_end - param->bit_start + 1;
 | |
| +		if (bits_total + bits > 64) {
 | |
| +			pr_err("Invalid fuse parameter segments; total bits = %d\n",
 | |
| +				bits_total + bits);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		fuse_val = readq_relaxed(fuse_base_addr
 | |
| +					 + param->row * BYTES_PER_FUSE_ROW);
 | |
| +		val = (fuse_val >> param->bit_start) & ((1ULL << bits) - 1);
 | |
| +		*param_value |= val << bits_total;
 | |
| +		bits_total += bits;
 | |
| +
 | |
| +		param++;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_convert_open_loop_voltage_fuse() - converts an open loop voltage fuse
 | |
| + *		value into an absolute voltage with units of microvolts
 | |
| + * @ref_volt:		Reference voltage in microvolts
 | |
| + * @step_volt:		The step size in microvolts of the fuse LSB
 | |
| + * @fuse:		Open loop voltage fuse value
 | |
| + * @fuse_len:		The bit length of the fuse value
 | |
| + *
 | |
| + * The MSB of the fuse parameter corresponds to a sign bit.  If it is set, then
 | |
| + * the lower bits correspond to the number of steps to go down from the
 | |
| + * reference voltage.  If it is not set, then the lower bits correspond to the
 | |
| + * number of steps to go up from the reference voltage.
 | |
| + */
 | |
| +int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
 | |
| +					int fuse_len)
 | |
| +{
 | |
| +	int sign, steps;
 | |
| +
 | |
| +	sign = (fuse & (1 << (fuse_len - 1))) ? -1 : 1;
 | |
| +	steps = fuse & ((1 << (fuse_len - 1)) - 1);
 | |
| +
 | |
| +	return ref_volt + sign * steps * step_volt;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_interpolate() - performs linear interpolation
 | |
| + * @x1		Lower known x value
 | |
| + * @y1		Lower known y value
 | |
| + * @x2		Upper known x value
 | |
| + * @y2		Upper known y value
 | |
| + * @x		Intermediate x value
 | |
| + *
 | |
| + * Returns y where (x, y) falls on the line between (x1, y1) and (x2, y2).
 | |
| + * It is required that x1 < x2, y1 <= y2, and x1 <= x <= x2.  If these
 | |
| + * conditions are not met, then y2 will be returned.
 | |
| + */
 | |
| +u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
 | |
| +{
 | |
| +	u64 temp;
 | |
| +
 | |
| +	if (x1 >= x2 || y1 > y2 || x1 > x || x > x2)
 | |
| +		return y2;
 | |
| +
 | |
| +	temp = (x2 - x) * (y2 - y1);
 | |
| +	do_div(temp, (u32)(x2 - x1));
 | |
| +
 | |
| +	return y2 - temp;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_array_property() - fill an array from a portion of the values
 | |
| + *		specified for a device tree property
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @prop_name:		The name of the device tree property to read from
 | |
| + * @tuple_size:		The number of elements in each tuple
 | |
| + * @out:		Output data array which must be of size tuple_size
 | |
| + *
 | |
| + * cpr3_parse_common_corner_data() must be called for vreg before this function
 | |
| + * is called so that fuse combo and speed bin size elements are initialized.
 | |
| + *
 | |
| + * Three formats are supported for the device tree property:
 | |
| + * 1. Length == tuple_size
 | |
| + *	(reading begins at index 0)
 | |
| + * 2. Length == tuple_size * vreg->fuse_combos_supported
 | |
| + *	(reading begins at index tuple_size * vreg->fuse_combo)
 | |
| + * 3. Length == tuple_size * vreg->speed_bins_supported
 | |
| + *	(reading begins at index tuple_size * vreg->speed_bin_fuse)
 | |
| + *
 | |
| + * All other property lengths are treated as errors.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_array_property(struct cpr3_regulator *vreg,
 | |
| +		const char *prop_name, int tuple_size, u32 *out)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	int len = 0;
 | |
| +	int i, offset, rc;
 | |
| +
 | |
| +	if (!of_find_property(node, prop_name, &len)) {
 | |
| +		cpr3_err(vreg, "property %s is missing\n", prop_name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (len == tuple_size * sizeof(u32)) {
 | |
| +		offset = 0;
 | |
| +	} else if (len == tuple_size * vreg->fuse_combos_supported
 | |
| +				     * sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->fuse_combo;
 | |
| +	} else if (vreg->speed_bins_supported > 0 &&
 | |
| +		 len == tuple_size * vreg->speed_bins_supported * sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->speed_bin_fuse;
 | |
| +	} else {
 | |
| +		if (vreg->speed_bins_supported > 0)
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * sizeof(u32),
 | |
| +				tuple_size * vreg->speed_bins_supported
 | |
| +					   * sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combos_supported
 | |
| +					   * sizeof(u32));
 | |
| +		else
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combos_supported
 | |
| +					   * sizeof(u32));
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < tuple_size; i++) {
 | |
| +		rc = of_property_read_u32_index(node, prop_name, offset + i,
 | |
| +						&out[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +				prop_name, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_corner_array_property() - fill a per-corner array from a portion
 | |
| + *		of the values specified for a device tree property
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @prop_name:		The name of the device tree property to read from
 | |
| + * @tuple_size:		The number of elements in each per-corner tuple
 | |
| + * @out:		Output data array which must be of size:
 | |
| + *			tuple_size * vreg->corner_count
 | |
| + *
 | |
| + * cpr3_parse_common_corner_data() must be called for vreg before this function
 | |
| + * is called so that fuse combo and speed bin size elements are initialized.
 | |
| + *
 | |
| + * Three formats are supported for the device tree property:
 | |
| + * 1. Length == tuple_size * vreg->corner_count
 | |
| + *	(reading begins at index 0)
 | |
| + * 2. Length == tuple_size * vreg->fuse_combo_corner_sum
 | |
| + *	(reading begins at index tuple_size * vreg->fuse_combo_offset)
 | |
| + * 3. Length == tuple_size * vreg->speed_bin_corner_sum
 | |
| + *	(reading begins at index tuple_size * vreg->speed_bin_offset)
 | |
| + *
 | |
| + * All other property lengths are treated as errors.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
 | |
| +		const char *prop_name, int tuple_size, u32 *out)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	int len = 0;
 | |
| +	int i, offset, rc;
 | |
| +
 | |
| +	if (!of_find_property(node, prop_name, &len)) {
 | |
| +		cpr3_err(vreg, "property %s is missing\n", prop_name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (len == tuple_size * vreg->corner_count * sizeof(u32)) {
 | |
| +		offset = 0;
 | |
| +	} else if (len == tuple_size * vreg->fuse_combo_corner_sum
 | |
| +				     * sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->fuse_combo_offset;
 | |
| +	} else if (vreg->speed_bin_corner_sum > 0 &&
 | |
| +		 len == tuple_size * vreg->speed_bin_corner_sum * sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->speed_bin_offset;
 | |
| +	} else {
 | |
| +		if (vreg->speed_bin_corner_sum > 0)
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * vreg->corner_count * sizeof(u32),
 | |
| +				tuple_size * vreg->speed_bin_corner_sum
 | |
| +					   * sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combo_corner_sum
 | |
| +					   * sizeof(u32));
 | |
| +		else
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * vreg->corner_count * sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combo_corner_sum
 | |
| +					   * sizeof(u32));
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < tuple_size * vreg->corner_count; i++) {
 | |
| +		rc = of_property_read_u32_index(node, prop_name, offset + i,
 | |
| +						&out[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +				prop_name, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_corner_band_array_property() - fill a per-corner band array
 | |
| + *		from a portion of the values specified for a device tree
 | |
| + *		property
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @prop_name:		The name of the device tree property to read from
 | |
| + * @tuple_size:		The number of elements in each per-corner band tuple
 | |
| + * @out:		Output data array which must be of size:
 | |
| + *			tuple_size * vreg->corner_band_count
 | |
| + *
 | |
| + * cpr3_parse_common_corner_data() must be called for vreg before this function
 | |
| + * is called so that fuse combo and speed bin size elements are initialized.
 | |
| + * In addition, corner band fuse combo and speed bin sum and offset elements
 | |
| + * must be initialized prior to executing this function.
 | |
| + *
 | |
| + * Three formats are supported for the device tree property:
 | |
| + * 1. Length == tuple_size * vreg->corner_band_count
 | |
| + *	(reading begins at index 0)
 | |
| + * 2. Length == tuple_size * vreg->fuse_combo_corner_band_sum
 | |
| + *	(reading begins at index tuple_size *
 | |
| + *		vreg->fuse_combo_corner_band_offset)
 | |
| + * 3. Length == tuple_size * vreg->speed_bin_corner_band_sum
 | |
| + *	(reading begins at index tuple_size *
 | |
| + *		vreg->speed_bin_corner_band_offset)
 | |
| + *
 | |
| + * All other property lengths are treated as errors.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
 | |
| +		const char *prop_name, int tuple_size, u32 *out)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	int len = 0;
 | |
| +	int i, offset, rc;
 | |
| +
 | |
| +	if (!of_find_property(node, prop_name, &len)) {
 | |
| +		cpr3_err(vreg, "property %s is missing\n", prop_name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (len == tuple_size * vreg->corner_band_count * sizeof(u32)) {
 | |
| +		offset = 0;
 | |
| +	} else if (len == tuple_size * vreg->fuse_combo_corner_band_sum
 | |
| +				     * sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->fuse_combo_corner_band_offset;
 | |
| +	} else if (vreg->speed_bin_corner_band_sum > 0 &&
 | |
| +		 len == tuple_size * vreg->speed_bin_corner_band_sum *
 | |
| +		   sizeof(u32)) {
 | |
| +		offset = tuple_size * vreg->speed_bin_corner_band_offset;
 | |
| +	} else {
 | |
| +		if (vreg->speed_bin_corner_band_sum > 0)
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * vreg->corner_band_count *
 | |
| +				 sizeof(u32),
 | |
| +				tuple_size * vreg->speed_bin_corner_band_sum
 | |
| +					   * sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combo_corner_band_sum
 | |
| +					   * sizeof(u32));
 | |
| +		else
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_size * vreg->corner_band_count *
 | |
| +				 sizeof(u32),
 | |
| +				tuple_size * vreg->fuse_combo_corner_band_sum
 | |
| +					   * sizeof(u32));
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < tuple_size * vreg->corner_band_count; i++) {
 | |
| +		rc = of_property_read_u32_index(node, prop_name, offset + i,
 | |
| +						&out[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +				prop_name, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_common_corner_data() - parse common CPR3 properties relating to
 | |
| + *		the corners supported by a CPR3 regulator from device tree
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function reads, validates, and utilizes the following device tree
 | |
| + * properties: qcom,cpr-fuse-corners, qcom,cpr-fuse-combos, qcom,cpr-speed-bins,
 | |
| + * qcom,cpr-speed-bin-corners, qcom,cpr-corners, qcom,cpr-voltage-ceiling,
 | |
| + * qcom,cpr-voltage-floor, qcom,corner-frequencies,
 | |
| + * and qcom,cpr-corner-fmax-map.
 | |
| + *
 | |
| + * It initializes these CPR3 regulator elements: corner, corner_count,
 | |
| + * fuse_combos_supported, fuse_corner_map, and speed_bins_supported.  It
 | |
| + * initializes these elements for each corner: ceiling_volt, floor_volt,
 | |
| + * proc_freq, and cpr_fuse_corner.
 | |
| + *
 | |
| + * It requires that the following CPR3 regulator elements be initialized before
 | |
| + * being called: fuse_corner_count, fuse_combo, and speed_bin_fuse.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	u32 max_fuse_combos, fuse_corners, aging_allowed = 0;
 | |
| +	u32 max_speed_bins = 0;
 | |
| +	u32 *combo_corners;
 | |
| +	u32 *speed_bin_corners;
 | |
| +	u32 *temp;
 | |
| +	int i, j, rc;
 | |
| +
 | |
| +	rc = of_property_read_u32(node, "qcom,cpr-fuse-corners", &fuse_corners);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading property qcom,cpr-fuse-corners, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->fuse_corner_count != fuse_corners) {
 | |
| +		cpr3_err(vreg, "device tree config supports %d fuse corners but the hardware has %d fuse corners\n",
 | |
| +			fuse_corners, vreg->fuse_corner_count);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	rc = of_property_read_u32(node, "qcom,cpr-fuse-combos",
 | |
| +				&max_fuse_combos);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading property qcom,cpr-fuse-combos, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Sanity check against arbitrarily large value to avoid excessive
 | |
| +	 * memory allocation.
 | |
| +	 */
 | |
| +	if (max_fuse_combos > 100 || max_fuse_combos == 0) {
 | |
| +		cpr3_err(vreg, "qcom,cpr-fuse-combos is invalid: %u\n",
 | |
| +			max_fuse_combos);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->fuse_combo >= max_fuse_combos) {
 | |
| +		cpr3_err(vreg, "device tree config supports fuse combos 0-%u but the hardware has combo %d\n",
 | |
| +			max_fuse_combos - 1, vreg->fuse_combo);
 | |
| +		BUG_ON(1);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	vreg->fuse_combos_supported = max_fuse_combos;
 | |
| +
 | |
| +	of_property_read_u32(node, "qcom,cpr-speed-bins", &max_speed_bins);
 | |
| +
 | |
| +	/*
 | |
| +	 * Sanity check against arbitrarily large value to avoid excessive
 | |
| +	 * memory allocation.
 | |
| +	 */
 | |
| +	if (max_speed_bins > 100) {
 | |
| +		cpr3_err(vreg, "qcom,cpr-speed-bins is invalid: %u\n",
 | |
| +			max_speed_bins);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (max_speed_bins && vreg->speed_bin_fuse >= max_speed_bins) {
 | |
| +		cpr3_err(vreg, "device tree config supports speed bins 0-%u but the hardware has speed bin %d\n",
 | |
| +			max_speed_bins - 1, vreg->speed_bin_fuse);
 | |
| +		BUG();
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	vreg->speed_bins_supported = max_speed_bins;
 | |
| +
 | |
| +	combo_corners = kcalloc(vreg->fuse_combos_supported,
 | |
| +				sizeof(*combo_corners), GFP_KERNEL);
 | |
| +	if (!combo_corners)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = of_property_read_u32_array(node, "qcom,cpr-corners", combo_corners,
 | |
| +					vreg->fuse_combos_supported);
 | |
| +	if (rc == -EOVERFLOW) {
 | |
| +		/* Single value case */
 | |
| +		rc = of_property_read_u32(node, "qcom,cpr-corners",
 | |
| +					combo_corners);
 | |
| +		for (i = 1; i < vreg->fuse_combos_supported; i++)
 | |
| +			combo_corners[i] = combo_corners[0];
 | |
| +	}
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading property qcom,cpr-corners, rc=%d\n",
 | |
| +			rc);
 | |
| +		kfree(combo_corners);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	vreg->fuse_combo_offset = 0;
 | |
| +	vreg->fuse_combo_corner_sum = 0;
 | |
| +	for (i = 0; i < vreg->fuse_combos_supported; i++) {
 | |
| +		vreg->fuse_combo_corner_sum += combo_corners[i];
 | |
| +		if (i < vreg->fuse_combo)
 | |
| +			vreg->fuse_combo_offset += combo_corners[i];
 | |
| +	}
 | |
| +
 | |
| +	vreg->corner_count = combo_corners[vreg->fuse_combo];
 | |
| +
 | |
| +	kfree(combo_corners);
 | |
| +
 | |
| +	vreg->speed_bin_offset = 0;
 | |
| +	vreg->speed_bin_corner_sum = 0;
 | |
| +	if (vreg->speed_bins_supported > 0) {
 | |
| +		speed_bin_corners = kcalloc(vreg->speed_bins_supported,
 | |
| +					sizeof(*speed_bin_corners), GFP_KERNEL);
 | |
| +		if (!speed_bin_corners)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		rc = of_property_read_u32_array(node,
 | |
| +				"qcom,cpr-speed-bin-corners", speed_bin_corners,
 | |
| +				vreg->speed_bins_supported);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property qcom,cpr-speed-bin-corners, rc=%d\n",
 | |
| +				rc);
 | |
| +			kfree(speed_bin_corners);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		for (i = 0; i < vreg->speed_bins_supported; i++) {
 | |
| +			vreg->speed_bin_corner_sum += speed_bin_corners[i];
 | |
| +			if (i < vreg->speed_bin_fuse)
 | |
| +				vreg->speed_bin_offset += speed_bin_corners[i];
 | |
| +		}
 | |
| +
 | |
| +		if (speed_bin_corners[vreg->speed_bin_fuse]
 | |
| +		    != vreg->corner_count) {
 | |
| +			cpr3_err(vreg, "qcom,cpr-corners and qcom,cpr-speed-bin-corners conflict on number of corners: %d vs %u\n",
 | |
| +				vreg->corner_count,
 | |
| +				speed_bin_corners[vreg->speed_bin_fuse]);
 | |
| +			kfree(speed_bin_corners);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		kfree(speed_bin_corners);
 | |
| +	}
 | |
| +
 | |
| +	vreg->corner = devm_kcalloc(ctrl->dev, vreg->corner_count,
 | |
| +				    sizeof(*vreg->corner), GFP_KERNEL);
 | |
| +	temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
 | |
| +	if (!vreg->corner || !temp)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-ceiling",
 | |
| +			1, temp);
 | |
| +	if (rc)
 | |
| +		goto free_temp;
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		vreg->corner[i].ceiling_volt
 | |
| +			= CPR3_ROUND(temp[i], ctrl->step_volt);
 | |
| +		vreg->corner[i].abs_ceiling_volt = vreg->corner[i].ceiling_volt;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-floor",
 | |
| +			1, temp);
 | |
| +	if (rc)
 | |
| +		goto free_temp;
 | |
| +	for (i = 0; i < vreg->corner_count; i++)
 | |
| +		vreg->corner[i].floor_volt
 | |
| +			= CPR3_ROUND(temp[i], ctrl->step_volt);
 | |
| +
 | |
| +	/* Validate ceiling and floor values */
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		if (vreg->corner[i].floor_volt
 | |
| +		    > vreg->corner[i].ceiling_volt) {
 | |
| +			cpr3_err(vreg, "CPR floor[%d]=%d > ceiling[%d]=%d uV\n",
 | |
| +				i, vreg->corner[i].floor_volt,
 | |
| +				i, vreg->corner[i].ceiling_volt);
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Load optional system-supply voltages */
 | |
| +	if (of_find_property(vreg->of_node, "qcom,system-voltage", NULL)) {
 | |
| +		rc = cpr3_parse_corner_array_property(vreg,
 | |
| +			"qcom,system-voltage", 1, temp);
 | |
| +		if (rc)
 | |
| +			goto free_temp;
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			vreg->corner[i].system_volt = temp[i];
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg, "qcom,corner-frequencies",
 | |
| +			1, temp);
 | |
| +	if (rc)
 | |
| +		goto free_temp;
 | |
| +	for (i = 0; i < vreg->corner_count; i++)
 | |
| +		vreg->corner[i].proc_freq = temp[i];
 | |
| +
 | |
| +	/* Validate frequencies */
 | |
| +	for (i = 1; i < vreg->corner_count; i++) {
 | |
| +		if (vreg->corner[i].proc_freq
 | |
| +		    < vreg->corner[i - 1].proc_freq) {
 | |
| +			cpr3_err(vreg, "invalid frequency: freq[%d]=%u < freq[%d]=%u\n",
 | |
| +				i, vreg->corner[i].proc_freq, i - 1,
 | |
| +				vreg->corner[i - 1].proc_freq);
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	vreg->fuse_corner_map = devm_kcalloc(ctrl->dev, vreg->fuse_corner_count,
 | |
| +				    sizeof(*vreg->fuse_corner_map), GFP_KERNEL);
 | |
| +	if (!vreg->fuse_corner_map) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto free_temp;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_array_property(vreg, "qcom,cpr-corner-fmax-map",
 | |
| +		vreg->fuse_corner_count, temp);
 | |
| +	if (rc)
 | |
| +		goto free_temp;
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +		vreg->fuse_corner_map[i] = temp[i] - CPR3_CORNER_OFFSET;
 | |
| +		if (temp[i] < CPR3_CORNER_OFFSET
 | |
| +		    || temp[i] > vreg->corner_count + CPR3_CORNER_OFFSET) {
 | |
| +			cpr3_err(vreg, "invalid corner value specified in qcom,cpr-corner-fmax-map: %u\n",
 | |
| +				temp[i]);
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		} else if (i > 0 && temp[i - 1] >= temp[i]) {
 | |
| +			cpr3_err(vreg, "invalid corner %u less than or equal to previous corner %u\n",
 | |
| +				temp[i], temp[i - 1]);
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		}
 | |
| +	}
 | |
| +	if (temp[vreg->fuse_corner_count - 1] != vreg->corner_count)
 | |
| +		cpr3_debug(vreg, "Note: highest Fmax corner %u in qcom,cpr-corner-fmax-map does not match highest supported corner %d\n",
 | |
| +			temp[vreg->fuse_corner_count - 1],
 | |
| +			vreg->corner_count);
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		for (j = 0; j < vreg->fuse_corner_count; j++) {
 | |
| +			if (i + CPR3_CORNER_OFFSET <= temp[j]) {
 | |
| +				vreg->corner[i].cpr_fuse_corner = j;
 | |
| +				break;
 | |
| +			}
 | |
| +		}
 | |
| +		if (j == vreg->fuse_corner_count) {
 | |
| +			/*
 | |
| +			 * Handle the case where the highest fuse corner maps
 | |
| +			 * to a corner below the highest corner.
 | |
| +			 */
 | |
| +			vreg->corner[i].cpr_fuse_corner
 | |
| +				= vreg->fuse_corner_count - 1;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +				"qcom,allow-aging-voltage-adjustment", NULL)) {
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +			"qcom,allow-aging-voltage-adjustment",
 | |
| +			1, &aging_allowed);
 | |
| +		if (rc)
 | |
| +			goto free_temp;
 | |
| +
 | |
| +		vreg->aging_allowed = aging_allowed;
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +		       "qcom,allow-aging-open-loop-voltage-adjustment", NULL)) {
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +			"qcom,allow-aging-open-loop-voltage-adjustment",
 | |
| +			1, &aging_allowed);
 | |
| +		if (rc)
 | |
| +			goto free_temp;
 | |
| +
 | |
| +		vreg->aging_allow_open_loop_adj = aging_allowed;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->aging_allowed) {
 | |
| +		if (ctrl->aging_ref_volt <= 0) {
 | |
| +			cpr3_err(ctrl, "qcom,cpr-aging-ref-voltage must be specified\n");
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +			"qcom,cpr-aging-max-voltage-adjustment",
 | |
| +			1, &vreg->aging_max_adjust_volt);
 | |
| +		if (rc)
 | |
| +			goto free_temp;
 | |
| +
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +			"qcom,cpr-aging-ref-corner", 1, &vreg->aging_corner);
 | |
| +		if (rc) {
 | |
| +			goto free_temp;
 | |
| +		} else if (vreg->aging_corner < CPR3_CORNER_OFFSET
 | |
| +			   || vreg->aging_corner > vreg->corner_count - 1
 | |
| +							+ CPR3_CORNER_OFFSET) {
 | |
| +			cpr3_err(vreg, "aging reference corner=%d not in range [%d, %d]\n",
 | |
| +				vreg->aging_corner, CPR3_CORNER_OFFSET,
 | |
| +				vreg->corner_count - 1 + CPR3_CORNER_OFFSET);
 | |
| +			rc = -EINVAL;
 | |
| +			goto free_temp;
 | |
| +		}
 | |
| +		vreg->aging_corner -= CPR3_CORNER_OFFSET;
 | |
| +
 | |
| +		if (of_find_property(vreg->of_node, "qcom,cpr-aging-derate",
 | |
| +					NULL)) {
 | |
| +			rc = cpr3_parse_corner_array_property(vreg,
 | |
| +				"qcom,cpr-aging-derate", 1, temp);
 | |
| +			if (rc)
 | |
| +				goto free_temp;
 | |
| +
 | |
| +			for (i = 0; i < vreg->corner_count; i++)
 | |
| +				vreg->corner[i].aging_derate = temp[i];
 | |
| +		} else {
 | |
| +			for (i = 0; i < vreg->corner_count; i++)
 | |
| +				vreg->corner[i].aging_derate
 | |
| +					= CPR3_AGING_DERATE_UNITY;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +free_temp:
 | |
| +	kfree(temp);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_thread_u32() - parse the specified property from the CPR3 thread's
 | |
| + *		device tree node and verify that it is within the allowed limits
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + * @propname:		The name of the device tree property to read
 | |
| + * @out_value:		The output pointer to fill with the value read
 | |
| + * @value_min:		The minimum allowed property value
 | |
| + * @value_max:		The maximum allowed property value
 | |
| + *
 | |
| + * This function prints a verbose error message if the property is missing or
 | |
| + * has a value which is not within the specified range.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
 | |
| +		       u32 *out_value, u32 value_min, u32 value_max)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = of_property_read_u32(thread->of_node, propname, out_value);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(thread->ctrl, "thread %u error reading property %s, rc=%d\n",
 | |
| +			thread->thread_id, propname, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (*out_value < value_min || *out_value > value_max) {
 | |
| +		cpr3_err(thread->ctrl, "thread %u %s=%u is invalid; allowed range: [%u, %u]\n",
 | |
| +			thread->thread_id, propname, *out_value, value_min,
 | |
| +			value_max);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_ctrl_u32() - parse the specified property from the CPR3
 | |
| + *		controller's device tree node and verify that it is within the
 | |
| + *		allowed limits
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + * @propname:		The name of the device tree property to read
 | |
| + * @out_value:		The output pointer to fill with the value read
 | |
| + * @value_min:		The minimum allowed property value
 | |
| + * @value_max:		The maximum allowed property value
 | |
| + *
 | |
| + * This function prints a verbose error message if the property is missing or
 | |
| + * has a value which is not within the specified range.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
 | |
| +		       u32 *out_value, u32 value_min, u32 value_max)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = of_property_read_u32(ctrl->dev->of_node, propname, out_value);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading property %s, rc=%d\n",
 | |
| +			propname, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (*out_value < value_min || *out_value > value_max) {
 | |
| +		cpr3_err(ctrl, "%s=%u is invalid; allowed range: [%u, %u]\n",
 | |
| +			propname, *out_value, value_min, value_max);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_common_thread_data() - parse common CPR3 thread properties from
 | |
| + *		device tree
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-up",
 | |
| +			&thread->consecutive_up, CPR3_CONSECUTIVE_UP_DOWN_MIN,
 | |
| +			CPR3_CONSECUTIVE_UP_DOWN_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-down",
 | |
| +			&thread->consecutive_down, CPR3_CONSECUTIVE_UP_DOWN_MIN,
 | |
| +			CPR3_CONSECUTIVE_UP_DOWN_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_thread_u32(thread, "qcom,cpr-up-threshold",
 | |
| +			&thread->up_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
 | |
| +			CPR3_UP_DOWN_THRESHOLD_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_thread_u32(thread, "qcom,cpr-down-threshold",
 | |
| +			&thread->down_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
 | |
| +			CPR3_UP_DOWN_THRESHOLD_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_irq_affinity() - parse CPR IRQ affinity information
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_parse_irq_affinity(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct device_node *cpu_node;
 | |
| +	int i, cpu;
 | |
| +	int len = 0;
 | |
| +
 | |
| +	if (!of_find_property(ctrl->dev->of_node, "qcom,cpr-interrupt-affinity",
 | |
| +				&len)) {
 | |
| +		/* No IRQ affinity required */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	len /= sizeof(u32);
 | |
| +
 | |
| +	for (i = 0; i < len; i++) {
 | |
| +		cpu_node = of_parse_phandle(ctrl->dev->of_node,
 | |
| +					    "qcom,cpr-interrupt-affinity", i);
 | |
| +		if (!cpu_node) {
 | |
| +			cpr3_err(ctrl, "could not find CPU node %d\n", i);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		for_each_possible_cpu(cpu) {
 | |
| +			if (of_get_cpu_node(cpu, NULL) == cpu_node) {
 | |
| +				cpumask_set_cpu(cpu, &ctrl->irq_affinity_mask);
 | |
| +				break;
 | |
| +			}
 | |
| +		}
 | |
| +		of_node_put(cpu_node);
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int cpr3_panic_notifier_init(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct device_node *node = ctrl->dev->of_node;
 | |
| +	struct cpr3_panic_regs_info *panic_regs_info;
 | |
| +	struct cpr3_reg_info *regs;
 | |
| +	int i, reg_count, len, rc = 0;
 | |
| +
 | |
| +	if (!of_find_property(node, "qcom,cpr-panic-reg-addr-list", &len)) {
 | |
| +		/* panic register address list not specified */
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	reg_count = len / sizeof(u32);
 | |
| +	if (!reg_count) {
 | |
| +		cpr3_err(ctrl, "qcom,cpr-panic-reg-addr-list has invalid len = %d\n",
 | |
| +			len);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (!of_find_property(node, "qcom,cpr-panic-reg-name-list", NULL)) {
 | |
| +		cpr3_err(ctrl, "property qcom,cpr-panic-reg-name-list not specified\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	len = of_property_count_strings(node, "qcom,cpr-panic-reg-name-list");
 | |
| +	if (reg_count != len) {
 | |
| +		cpr3_err(ctrl, "qcom,cpr-panic-reg-name-list should have %d strings\n",
 | |
| +			reg_count);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	panic_regs_info = devm_kzalloc(ctrl->dev, sizeof(*panic_regs_info),
 | |
| +					GFP_KERNEL);
 | |
| +	if (!panic_regs_info)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	regs = devm_kcalloc(ctrl->dev, reg_count, sizeof(*regs), GFP_KERNEL);
 | |
| +	if (!regs)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	for (i = 0; i < reg_count; i++) {
 | |
| +		rc = of_property_read_string_index(node,
 | |
| +				"qcom,cpr-panic-reg-name-list", i,
 | |
| +				&(regs[i].name));
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-name-list, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		rc = of_property_read_u32_index(node,
 | |
| +				"qcom,cpr-panic-reg-addr-list", i,
 | |
| +				&(regs[i].addr));
 | |
| +		if (rc) {
 | |
| +			cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-addr-list, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +		regs[i].virt_addr = devm_ioremap(ctrl->dev, regs[i].addr, 0x4);
 | |
| +		if (!regs[i].virt_addr) {
 | |
| +			pr_err("Unable to map panic register addr 0x%08x\n",
 | |
| +				regs[i].addr);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +		regs[i].value = 0xFFFFFFFF;
 | |
| +	}
 | |
| +
 | |
| +	panic_regs_info->reg_count = reg_count;
 | |
| +	panic_regs_info->regs = regs;
 | |
| +	ctrl->panic_regs_info = panic_regs_info;
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_common_ctrl_data() - parse common CPR3 controller properties from
 | |
| + *		device tree
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-sensor-time",
 | |
| +			&ctrl->sensor_time, 0, UINT_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-loop-time",
 | |
| +			&ctrl->loop_time, 0, UINT_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-idle-cycles",
 | |
| +			&ctrl->idle_clocks, CPR3_IDLE_CLOCKS_MIN,
 | |
| +			CPR3_IDLE_CLOCKS_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-min",
 | |
| +			&ctrl->step_quot_init_min, CPR3_STEP_QUOT_MIN,
 | |
| +			CPR3_STEP_QUOT_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-max",
 | |
| +			&ctrl->step_quot_init_max, CPR3_STEP_QUOT_MIN,
 | |
| +			CPR3_STEP_QUOT_MAX);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
 | |
| +				&ctrl->step_volt);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	if (ctrl->step_volt <= 0) {
 | |
| +		cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
 | |
| +			ctrl->step_volt);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-count-mode",
 | |
| +			&ctrl->count_mode, CPR3_COUNT_MODE_ALL_AT_ONCE_MIN,
 | |
| +			CPR3_COUNT_MODE_STAGGERED);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	/* Count repeat is optional */
 | |
| +	ctrl->count_repeat = 0;
 | |
| +	of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-count-repeat",
 | |
| +			&ctrl->count_repeat);
 | |
| +
 | |
| +	ctrl->cpr_allowed_sw =
 | |
| +		of_property_read_bool(ctrl->dev->of_node, "qcom,cpr-enable") ||
 | |
| +		ctrl->cpr_global_setting == CPR_CLOSED_LOOP_EN;
 | |
| +
 | |
| +	rc = cpr3_parse_irq_affinity(ctrl);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	/* Aging reference voltage is optional */
 | |
| +	ctrl->aging_ref_volt = 0;
 | |
| +	of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-aging-ref-voltage",
 | |
| +			&ctrl->aging_ref_volt);
 | |
| +
 | |
| +	/* Aging possible bitmask is optional */
 | |
| +	ctrl->aging_possible_mask = 0;
 | |
| +	of_property_read_u32(ctrl->dev->of_node,
 | |
| +			"qcom,cpr-aging-allowed-reg-mask",
 | |
| +			&ctrl->aging_possible_mask);
 | |
| +
 | |
| +	if (ctrl->aging_possible_mask) {
 | |
| +		/*
 | |
| +		 * Aging possible register value required if bitmask is
 | |
| +		 * specified
 | |
| +		 */
 | |
| +		rc = cpr3_parse_ctrl_u32(ctrl,
 | |
| +				"qcom,cpr-aging-allowed-reg-value",
 | |
| +				&ctrl->aging_possible_val, 0, UINT_MAX);
 | |
| +		if (rc)
 | |
| +			return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(ctrl->dev->of_node, "clock-names", NULL)) {
 | |
| +		ctrl->core_clk = devm_clk_get(ctrl->dev, "core_clk");
 | |
| +		if (IS_ERR(ctrl->core_clk)) {
 | |
| +			rc = PTR_ERR(ctrl->core_clk);
 | |
| +			if (rc != -EPROBE_DEFER)
 | |
| +				cpr3_err(ctrl, "unable request core clock, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_panic_notifier_init(ctrl);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
 | |
| +		ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
 | |
| +		if (IS_ERR(ctrl->vdd_regulator)) {
 | |
| +			rc = PTR_ERR(ctrl->vdd_regulator);
 | |
| +			if (rc != -EPROBE_DEFER)
 | |
| +				cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
 | |
| +					 rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	} else {
 | |
| +		cpr3_err(ctrl, "vdd supply is not defined\n");
 | |
| +		return -ENODEV;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
 | |
| +								"system");
 | |
| +	if (IS_ERR(ctrl->system_regulator)) {
 | |
| +		rc = PTR_ERR(ctrl->system_regulator);
 | |
| +		if (rc != -EPROBE_DEFER) {
 | |
| +			rc = 0;
 | |
| +			ctrl->system_regulator = NULL;
 | |
| +		} else {
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
 | |
| +							      "mem-acc");
 | |
| +	if (IS_ERR(ctrl->mem_acc_regulator)) {
 | |
| +		rc = PTR_ERR(ctrl->mem_acc_regulator);
 | |
| +		if (rc != -EPROBE_DEFER) {
 | |
| +			rc = 0;
 | |
| +			ctrl->mem_acc_regulator = NULL;
 | |
| +		} else {
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_open_loop_common_ctrl_data() - parse common open loop CPR3
 | |
| + *			controller properties from device tree
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
 | |
| +				  &ctrl->step_volt);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->step_volt <= 0) {
 | |
| +		cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
 | |
| +			 ctrl->step_volt);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
 | |
| +		ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
 | |
| +		if (IS_ERR(ctrl->vdd_regulator)) {
 | |
| +			rc = PTR_ERR(ctrl->vdd_regulator);
 | |
| +			if (rc != -EPROBE_DEFER)
 | |
| +				cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
 | |
| +					 rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	} else {
 | |
| +		cpr3_err(ctrl, "vdd supply is not defined\n");
 | |
| +		return -ENODEV;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
 | |
| +								"system");
 | |
| +	if (IS_ERR(ctrl->system_regulator)) {
 | |
| +		rc = PTR_ERR(ctrl->system_regulator);
 | |
| +		if (rc != -EPROBE_DEFER) {
 | |
| +			rc = 0;
 | |
| +			ctrl->system_regulator = NULL;
 | |
| +		} else {
 | |
| +			return rc;
 | |
| +		}
 | |
| +	} else {
 | |
| +		rc = regulator_enable(ctrl->system_regulator);
 | |
| +	}
 | |
| +
 | |
| +	ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
 | |
| +							      "mem-acc");
 | |
| +	if (IS_ERR(ctrl->mem_acc_regulator)) {
 | |
| +		rc = PTR_ERR(ctrl->mem_acc_regulator);
 | |
| +		if (rc != -EPROBE_DEFER) {
 | |
| +			rc = 0;
 | |
| +			ctrl->mem_acc_regulator = NULL;
 | |
| +		} else {
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_limit_open_loop_voltages() - modify the open-loop voltage of each corner
 | |
| + *				so that it fits within the floor to ceiling
 | |
| + *				voltage range of the corner
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function clips the open-loop voltage for each corner so that it is
 | |
| + * limited to the floor to ceiling range.  It also rounds each open-loop voltage
 | |
| + * so that it corresponds to a set point available to the underlying regulator.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i, volt;
 | |
| +
 | |
| +	cpr3_debug(vreg, "open-loop voltages after trimming and rounding:\n");
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		volt = CPR3_ROUND(vreg->corner[i].open_loop_volt,
 | |
| +					vreg->thread->ctrl->step_volt);
 | |
| +		if (volt < vreg->corner[i].floor_volt)
 | |
| +			volt = vreg->corner[i].floor_volt;
 | |
| +		else if (volt > vreg->corner[i].ceiling_volt)
 | |
| +			volt = vreg->corner[i].ceiling_volt;
 | |
| +		vreg->corner[i].open_loop_volt = volt;
 | |
| +		cpr3_debug(vreg, "corner[%2d]: open-loop=%d uV\n", i, volt);
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_open_loop_voltage_as_ceiling() - configures the ceiling voltage for each
 | |
| + *		corner to equal the open-loop voltage if the relevant device
 | |
| + *		tree property is found for the CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function assumes that the the open-loop voltage for each corner has
 | |
| + * already been rounded to the nearest allowed set point and that it falls
 | |
| + * within the floor to ceiling range.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	if (!of_property_read_bool(vreg->of_node,
 | |
| +				"qcom,cpr-scaled-open-loop-voltage-as-ceiling"))
 | |
| +		return;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++)
 | |
| +		vreg->corner[i].ceiling_volt
 | |
| +			= vreg->corner[i].open_loop_volt;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_limit_floor_voltages() - raise the floor voltage of each corner so that
 | |
| + *		the optional maximum floor to ceiling voltage range specified in
 | |
| + *		device tree is satisfied
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function also ensures that the open-loop voltage for each corner falls
 | |
| + * within the final floor to ceiling voltage range and that floor voltages
 | |
| + * increase monotonically.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	char *prop = "qcom,cpr-floor-to-ceiling-max-range";
 | |
| +	int i, floor_new;
 | |
| +	u32 *floor_range;
 | |
| +	int rc = 0;
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop, NULL))
 | |
| +		goto enforce_monotonicity;
 | |
| +
 | |
| +	floor_range = kcalloc(vreg->corner_count, sizeof(*floor_range),
 | |
| +				GFP_KERNEL);
 | |
| +	if (!floor_range)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg, prop, 1, floor_range);
 | |
| +	if (rc)
 | |
| +		goto free_floor_adjust;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		if ((s32)floor_range[i] >= 0) {
 | |
| +			floor_new = CPR3_ROUND(vreg->corner[i].ceiling_volt
 | |
| +							- floor_range[i],
 | |
| +						vreg->thread->ctrl->step_volt);
 | |
| +
 | |
| +			vreg->corner[i].floor_volt = max(floor_new,
 | |
| +						vreg->corner[i].floor_volt);
 | |
| +			if (vreg->corner[i].open_loop_volt
 | |
| +			    < vreg->corner[i].floor_volt)
 | |
| +				vreg->corner[i].open_loop_volt
 | |
| +					= vreg->corner[i].floor_volt;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +free_floor_adjust:
 | |
| +	kfree(floor_range);
 | |
| +
 | |
| +enforce_monotonicity:
 | |
| +	/* Ensure that floor voltages increase monotonically. */
 | |
| +	for (i = 1; i < vreg->corner_count; i++) {
 | |
| +		if (vreg->corner[i].floor_volt
 | |
| +		    < vreg->corner[i - 1].floor_volt) {
 | |
| +			cpr3_debug(vreg, "corner %d floor voltage=%d uV < corner %d voltage=%d uV; overriding: corner %d voltage=%d\n",
 | |
| +				i, vreg->corner[i].floor_volt,
 | |
| +				i - 1, vreg->corner[i - 1].floor_volt,
 | |
| +				i, vreg->corner[i - 1].floor_volt);
 | |
| +			vreg->corner[i].floor_volt
 | |
| +				= vreg->corner[i - 1].floor_volt;
 | |
| +
 | |
| +			if (vreg->corner[i].open_loop_volt
 | |
| +			    < vreg->corner[i].floor_volt)
 | |
| +				vreg->corner[i].open_loop_volt
 | |
| +					= vreg->corner[i].floor_volt;
 | |
| +			if (vreg->corner[i].ceiling_volt
 | |
| +			    < vreg->corner[i].floor_volt)
 | |
| +				vreg->corner[i].ceiling_volt
 | |
| +					= vreg->corner[i].floor_volt;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_print_quots() - print CPR target quotients into the kernel log for
 | |
| + *		debugging purposes
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +void cpr3_print_quots(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i, j, pos;
 | |
| +	size_t buflen;
 | |
| +	char *buf;
 | |
| +
 | |
| +	buflen = sizeof(*buf) * CPR3_RO_COUNT * (MAX_CHARS_PER_INT + 2);
 | |
| +	buf = kzalloc(buflen, GFP_KERNEL);
 | |
| +	if (!buf)
 | |
| +		return;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		for (j = 0, pos = 0; j < CPR3_RO_COUNT; j++)
 | |
| +			pos += scnprintf(buf + pos, buflen - pos, " %u",
 | |
| +				vreg->corner[i].target_quot[j]);
 | |
| +		cpr3_debug(vreg, "target quots[%2d]:%s\n", i, buf);
 | |
| +	}
 | |
| +
 | |
| +	kfree(buf);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_determine_part_type() - determine the part type (SS/TT/FF).
 | |
| + *
 | |
| + * qcom,cpr-part-types prop tells the number of part types for which correction
 | |
| + * voltages are different. Another prop qcom,cpr-parts-voltage will contain the
 | |
| + * open loop fuse voltage which will be compared with this part voltage
 | |
| + * and accordingly part type will de determined.
 | |
| + *
 | |
| + * if qcom,cpr-part-types has value n, then qcom,cpr-parts-voltage will be
 | |
| + * array of n - 1 elements which will contain the voltage in increasing order.
 | |
| + * This function compares the fused volatge with all these voltage and returns
 | |
| + * the first index for which the fused volatge is greater.
 | |
| + *
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @fuse_volt:		fused open loop voltage which will be compared with
 | |
| + *                      qcom,cpr-parts-voltage array
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
 | |
| +{
 | |
| +	int i, rc, len;
 | |
| +	u32 volt;
 | |
| +	int soc_version_major;
 | |
| +	char prop_name[100];
 | |
| +	const char prop_name_def[] = "qcom,cpr-parts-voltage";
 | |
| +	const char prop_name_v2[] = "qcom,cpr-parts-voltage-v2";
 | |
| +
 | |
| +	soc_version_major = read_ipq_soc_version_major();
 | |
| +        BUG_ON(soc_version_major <= 0);
 | |
| +
 | |
| +	if (of_property_read_u32(vreg->of_node, "qcom,cpr-part-types",
 | |
| +				  &vreg->part_type_supported))
 | |
| +		return 0;
 | |
| +
 | |
| +	if (soc_version_major > 1)
 | |
| +		strlcpy(prop_name, prop_name_v2, sizeof(prop_name_v2));
 | |
| +	else
 | |
| +		strlcpy(prop_name, prop_name_def, sizeof(prop_name_def));
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop_name, &len)) {
 | |
| +		cpr3_err(vreg, "property %s is missing\n", prop_name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (len != (vreg->part_type_supported - 1) * sizeof(u32)) {
 | |
| +		cpr3_err(vreg, "wrong len in qcom,cpr-parts-voltage\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->part_type_supported - 1; i++) {
 | |
| +		rc = of_property_read_u32_index(vreg->of_node,
 | |
| +					prop_name, i, &volt);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +				 prop_name, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		if (fuse_volt < volt)
 | |
| +			break;
 | |
| +	}
 | |
| +
 | |
| +	vreg->part_type = i;
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
 | |
| +		int *fuse_volt)
 | |
| +{
 | |
| +	int i, rc, prev_volt;
 | |
| +	int *volt_adjust;
 | |
| +	char prop_str[75];
 | |
| +	int soc_version_major = read_ipq_soc_version_major();
 | |
| +
 | |
| +	BUG_ON(soc_version_major <= 0);
 | |
| +
 | |
| +	if (vreg->part_type_supported) {
 | |
| +		if (soc_version_major > 1)
 | |
| +			snprintf(prop_str, sizeof(prop_str),
 | |
| +			"qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
 | |
| +			vreg->part_type);
 | |
| +		else
 | |
| +			snprintf(prop_str, sizeof(prop_str),
 | |
| +			"qcom,cpr-cold-temp-voltage-adjustment-%d",
 | |
| +			vreg->part_type);
 | |
| +	} else {
 | |
| +		strlcpy(prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
 | |
| +			sizeof(prop_str));
 | |
| +	}
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop_str, NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +		cpr3_info(vreg, "No cold temperature adjustment required.\n");
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
 | |
| +	GFP_KERNEL);
 | |
| +	if (!volt_adjust)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_array_property(vreg, prop_str,
 | |
| +			vreg->fuse_corner_count, volt_adjust);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load cold temp voltage adjustments, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (volt_adjust[i]) {
 | |
| +			prev_volt = fuse_volt[i];
 | |
| +			fuse_volt[i] += volt_adjust[i];
 | |
| +			cpr3_debug(vreg,
 | |
| +				"adjusted fuse corner %d open-loop voltage: %d -> %d uV\n",
 | |
| +				i, prev_volt, fuse_volt[i]);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(volt_adjust);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_can_adjust_cold_temp() - Is cold temperature adjustment available
 | |
| + *
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function checks the cold temperature threshold is available
 | |
| + *
 | |
| + * Return: true on cold temperature threshold is available, else false
 | |
| + */
 | |
| +bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	char prop_str[75];
 | |
| +	int soc_version_major = read_ipq_soc_version_major();
 | |
| +
 | |
| +	BUG_ON(soc_version_major <= 0);
 | |
| +
 | |
| +	if (soc_version_major > 1)
 | |
| +		strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
 | |
| +			sizeof(prop_str));
 | |
| +	else
 | |
| +		strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
 | |
| +			sizeof(prop_str));
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop_str, NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +		return false;
 | |
| +	} else
 | |
| +		return true;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_get_cold_temp_threshold() - get cold temperature threshold
 | |
| + *
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @cold_temp:		cold temperature read.
 | |
| + *
 | |
| + * This function reads the cold temperature threshold below which
 | |
| + * cold temperature adjustment margins will be applied.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
 | |
| +{
 | |
| +	int rc;
 | |
| +	u32 temp;
 | |
| +	char req_prop_str[75], prop_str[75];
 | |
| +	int soc_version_major = read_ipq_soc_version_major();
 | |
| +
 | |
| +	BUG_ON(soc_version_major <= 0);
 | |
| +
 | |
| +	if (vreg->part_type_supported) {
 | |
| +		if (soc_version_major > 1)
 | |
| +			snprintf(req_prop_str, sizeof(req_prop_str),
 | |
| +			"qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
 | |
| +			vreg->part_type);
 | |
| +		else
 | |
| +			snprintf(req_prop_str, sizeof(req_prop_str),
 | |
| +			"qcom,cpr-cold-temp-voltage-adjustment-%d",
 | |
| +			vreg->part_type);
 | |
| +	} else {
 | |
| +		strlcpy(req_prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
 | |
| +			sizeof(req_prop_str));
 | |
| +	}
 | |
| +
 | |
| +	if (soc_version_major > 1)
 | |
| +		strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
 | |
| +			sizeof(prop_str));
 | |
| +	else
 | |
| +		strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
 | |
| +			sizeof(prop_str));
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, req_prop_str, NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +		cpr3_info(vreg, "Cold temperature adjustment not required.\n");
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop_str, NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +                cpr3_err(vreg, "Missing %s required for %s\n",
 | |
| +			prop_str, req_prop_str);
 | |
| +		return -EINVAL;
 | |
| +        }
 | |
| +
 | |
| +	rc = of_property_read_u32(vreg->of_node, prop_str, &temp);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +			prop_str, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	*cold_temp = temp;
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_adjust_fused_open_loop_voltages() - adjust the fused open-loop voltages
 | |
| + *		for each fuse corner according to device tree values
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @fuse_volt:		Pointer to an array of the fused open-loop voltage
 | |
| + *			values
 | |
| + *
 | |
| + * Voltage values in fuse_volt are modified in place.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
 | |
| +		int *fuse_volt)
 | |
| +{
 | |
| +	int i, rc, prev_volt;
 | |
| +	int *volt_adjust;
 | |
| +	char prop_str[75];
 | |
| +	int soc_version_major = read_ipq_soc_version_major();
 | |
| +
 | |
| +	BUG_ON(soc_version_major <= 0);
 | |
| +
 | |
| +	if (vreg->part_type_supported) {
 | |
| +		if (soc_version_major > 1)
 | |
| +			snprintf(prop_str, sizeof(prop_str),
 | |
| +			"qcom,cpr-open-loop-voltage-fuse-adjustment-v2-%d",
 | |
| +			vreg->part_type);
 | |
| +		else
 | |
| +		snprintf(prop_str, sizeof(prop_str),
 | |
| +			 "qcom,cpr-open-loop-voltage-fuse-adjustment-%d",
 | |
| +			 vreg->part_type);
 | |
| +	} else {
 | |
| +		strlcpy(prop_str, "qcom,cpr-open-loop-voltage-fuse-adjustment",
 | |
| +			sizeof(prop_str));
 | |
| +	}
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, prop_str, NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
 | |
| +				GFP_KERNEL);
 | |
| +	if (!volt_adjust)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_array_property(vreg,
 | |
| +		prop_str, vreg->fuse_corner_count, volt_adjust);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load open-loop fused voltage adjustments, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (volt_adjust[i]) {
 | |
| +			prev_volt = fuse_volt[i];
 | |
| +			fuse_volt[i] += volt_adjust[i];
 | |
| +			cpr3_debug(vreg, "adjusted fuse corner %d open-loop voltage: %d --> %d uV\n",
 | |
| +				i, prev_volt, fuse_volt[i]);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(volt_adjust);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_adjust_open_loop_voltages() - adjust the open-loop voltages for each
 | |
| + *		corner according to device tree values
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i, rc, prev_volt, min_volt;
 | |
| +	int *volt_adjust, *volt_diff;
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node,
 | |
| +			"qcom,cpr-open-loop-voltage-adjustment", NULL)) {
 | |
| +		/* No adjustment required. */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
 | |
| +				GFP_KERNEL);
 | |
| +	volt_diff = kcalloc(vreg->corner_count, sizeof(*volt_diff), GFP_KERNEL);
 | |
| +	if (!volt_adjust || !volt_diff) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg,
 | |
| +		"qcom,cpr-open-loop-voltage-adjustment", 1, volt_adjust);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load open-loop voltage adjustments, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		if (volt_adjust[i]) {
 | |
| +			prev_volt = vreg->corner[i].open_loop_volt;
 | |
| +			vreg->corner[i].open_loop_volt += volt_adjust[i];
 | |
| +			cpr3_debug(vreg, "adjusted corner %d open-loop voltage: %d --> %d uV\n",
 | |
| +				i, prev_volt, vreg->corner[i].open_loop_volt);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +			"qcom,cpr-open-loop-voltage-min-diff", NULL)) {
 | |
| +		rc = cpr3_parse_corner_array_property(vreg,
 | |
| +			"qcom,cpr-open-loop-voltage-min-diff", 1, volt_diff);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "could not load minimum open-loop voltage differences, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Ensure that open-loop voltages increase monotonically with respect
 | |
| +	 * to configurable minimum allowed differences.
 | |
| +	 */
 | |
| +	for (i = 1; i < vreg->corner_count; i++) {
 | |
| +		min_volt = vreg->corner[i - 1].open_loop_volt + volt_diff[i];
 | |
| +		if (vreg->corner[i].open_loop_volt < min_volt) {
 | |
| +			cpr3_debug(vreg, "adjusted corner %d open-loop voltage=%d uV < corner %d voltage=%d uV + min diff=%d uV; overriding: corner %d voltage=%d\n",
 | |
| +				i, vreg->corner[i].open_loop_volt,
 | |
| +				i - 1, vreg->corner[i - 1].open_loop_volt,
 | |
| +				volt_diff[i], i, min_volt);
 | |
| +			vreg->corner[i].open_loop_volt = min_volt;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(volt_diff);
 | |
| +	kfree(volt_adjust);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_quot_adjustment() - returns the quotient adjustment value resulting from
 | |
| + *		the specified voltage adjustment and RO scaling factor
 | |
| + * @ro_scale:		The CPR ring oscillator (RO) scaling factor with units
 | |
| + *			of QUOT/V
 | |
| + * @volt_adjust:	The amount to adjust the voltage by in units of
 | |
| + *			microvolts.  This value may be positive or negative.
 | |
| + */
 | |
| +int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
 | |
| +{
 | |
| +	unsigned long long temp;
 | |
| +	int quot_adjust;
 | |
| +	int sign = 1;
 | |
| +
 | |
| +	if (ro_scale < 0) {
 | |
| +		sign = -sign;
 | |
| +		ro_scale = -ro_scale;
 | |
| +	}
 | |
| +
 | |
| +	if (volt_adjust < 0) {
 | |
| +		sign = -sign;
 | |
| +		volt_adjust = -volt_adjust;
 | |
| +	}
 | |
| +
 | |
| +	temp = (unsigned long long)ro_scale * (unsigned long long)volt_adjust;
 | |
| +	do_div(temp, 1000000);
 | |
| +
 | |
| +	quot_adjust = temp;
 | |
| +	quot_adjust *= sign;
 | |
| +
 | |
| +	return quot_adjust;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_voltage_adjustment() - returns the voltage adjustment value resulting
 | |
| + *		from the specified quotient adjustment and RO scaling factor
 | |
| + * @ro_scale:		The CPR ring oscillator (RO) scaling factor with units
 | |
| + *			of QUOT/V
 | |
| + * @quot_adjust:	The amount to adjust the quotient by in units of
 | |
| + *			QUOT.  This value may be positive or negative.
 | |
| + */
 | |
| +int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
 | |
| +{
 | |
| +	unsigned long long temp;
 | |
| +	int volt_adjust;
 | |
| +	int sign = 1;
 | |
| +
 | |
| +	if (ro_scale < 0) {
 | |
| +		sign = -sign;
 | |
| +		ro_scale = -ro_scale;
 | |
| +	}
 | |
| +
 | |
| +	if (quot_adjust < 0) {
 | |
| +		sign = -sign;
 | |
| +		quot_adjust = -quot_adjust;
 | |
| +	}
 | |
| +
 | |
| +	if (ro_scale == 0)
 | |
| +		return 0;
 | |
| +
 | |
| +	temp = (unsigned long long)quot_adjust * 1000000;
 | |
| +	do_div(temp, ro_scale);
 | |
| +
 | |
| +	volt_adjust = temp;
 | |
| +	volt_adjust *= sign;
 | |
| +
 | |
| +	return volt_adjust;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_parse_closed_loop_voltage_adjustments() - load per-fuse-corner and
 | |
| + *		per-corner closed-loop adjustment values from device tree
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @ro_sel:		Array of ring oscillator values selected for each
 | |
| + *			fuse corner
 | |
| + * @volt_adjust:	Pointer to array which will be filled with the
 | |
| + *			per-corner closed-loop adjustment voltages
 | |
| + * @volt_adjust_fuse:	Pointer to array which will be filled with the
 | |
| + *			per-fuse-corner closed-loop adjustment voltages
 | |
| + * @ro_scale:		Pointer to array which will be filled with the
 | |
| + *			per-fuse-corner RO scaling factor values with units of
 | |
| + *			QUOT/V
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_parse_closed_loop_voltage_adjustments(
 | |
| +			struct cpr3_regulator *vreg, u64 *ro_sel,
 | |
| +			int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
 | |
| +{
 | |
| +	int i, rc;
 | |
| +	u32 *ro_all_scale;
 | |
| +
 | |
| +	char volt_adj[] = "qcom,cpr-closed-loop-voltage-adjustment";
 | |
| +	char volt_fuse_adj[] = "qcom,cpr-closed-loop-voltage-fuse-adjustment";
 | |
| +	char ro_scaling[] = "qcom,cpr-ro-scaling-factor";
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node, volt_adj, NULL)
 | |
| +	    && !of_find_property(vreg->of_node, volt_fuse_adj, NULL)
 | |
| +	    && !vreg->aging_allowed) {
 | |
| +		/* No adjustment required. */
 | |
| +		return 0;
 | |
| +	} else if (!of_find_property(vreg->of_node, ro_scaling, NULL)) {
 | |
| +		cpr3_err(vreg, "Missing %s required for closed-loop voltage adjustment.\n",
 | |
| +				ro_scaling);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	ro_all_scale = kcalloc(vreg->fuse_corner_count * CPR3_RO_COUNT,
 | |
| +				sizeof(*ro_all_scale), GFP_KERNEL);
 | |
| +	if (!ro_all_scale)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_array_property(vreg, ro_scaling,
 | |
| +		vreg->fuse_corner_count * CPR3_RO_COUNT, ro_all_scale);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++)
 | |
| +		ro_scale[i] = ro_all_scale[i * CPR3_RO_COUNT + ro_sel[i]];
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++)
 | |
| +		memcpy(vreg->corner[i].ro_scale,
 | |
| +		 &ro_all_scale[vreg->corner[i].cpr_fuse_corner * CPR3_RO_COUNT],
 | |
| +		 sizeof(*ro_all_scale) * CPR3_RO_COUNT);
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node, volt_fuse_adj, NULL)) {
 | |
| +		rc = cpr3_parse_array_property(vreg, volt_fuse_adj,
 | |
| +			vreg->fuse_corner_count, volt_adjust_fuse);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "could not load closed-loop fused voltage adjustments, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node, volt_adj, NULL)) {
 | |
| +		rc = cpr3_parse_corner_array_property(vreg, volt_adj,
 | |
| +			1, volt_adjust);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(ro_all_scale);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_apm_init() - initialize APM data for a CPR3 controller
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * This function loads memory array power mux (APM) data from device tree
 | |
| + * if it is present and requests a handle to the appropriate APM controller
 | |
| + * device.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_apm_init(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct device_node *node = ctrl->dev->of_node;
 | |
| +	int rc;
 | |
| +
 | |
| +	if (!of_find_property(node, "qcom,apm-ctrl", NULL)) {
 | |
| +		/* No APM used */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->apm = msm_apm_ctrl_dev_get(ctrl->dev);
 | |
| +	if (IS_ERR(ctrl->apm)) {
 | |
| +		rc = PTR_ERR(ctrl->apm);
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "APM get failed, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = of_property_read_u32(node, "qcom,apm-threshold-voltage",
 | |
| +				&ctrl->apm_threshold_volt);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading qcom,apm-threshold-voltage, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	ctrl->apm_threshold_volt
 | |
| +		= CPR3_ROUND(ctrl->apm_threshold_volt, ctrl->step_volt);
 | |
| +
 | |
| +	/* No error check since this is an optional property. */
 | |
| +	of_property_read_u32(node, "qcom,apm-hysteresis-voltage",
 | |
| +				&ctrl->apm_adj_volt);
 | |
| +	ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
 | |
| +
 | |
| +	ctrl->apm_high_supply = MSM_APM_SUPPLY_APCC;
 | |
| +	ctrl->apm_low_supply = MSM_APM_SUPPLY_MX;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_mem_acc_init() - initialize mem-acc regulator data for
 | |
| + *		a CPR3 regulator
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	u32 *temp;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	if (!ctrl->mem_acc_regulator) {
 | |
| +		cpr3_info(ctrl, "not using memory accelerator regulator\n");
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
 | |
| +	if (!temp)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg, "qcom,mem-acc-voltage",
 | |
| +					      1, temp);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "could not load mem-acc corners, rc=%d\n", rc);
 | |
| +	} else {
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			vreg->corner[i].mem_acc_volt = temp[i];
 | |
| +	}
 | |
| +
 | |
| +	kfree(temp);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_load_core_and_temp_adj() - parse amount of voltage adjustment for
 | |
| + *		per-online-core and per-temperature voltage adjustment for a
 | |
| + *		given corner or corner band from device tree.
 | |
| + * @vreg:	Pointer to the CPR3 regulator
 | |
| + * @num:	Corner number or corner band number
 | |
| + * @use_corner_band:	Boolean indicating if the CPR3 regulator supports
 | |
| + *			adjustments per corner band
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_load_core_and_temp_adj(struct cpr3_regulator *vreg,
 | |
| +					int num, bool use_corner_band)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	struct cpr4_sdelta *sdelta;
 | |
| +	int sdelta_size, i, j, pos, rc = 0;
 | |
| +	char str[75];
 | |
| +	size_t buflen;
 | |
| +	char *buf;
 | |
| +
 | |
| +	sdelta = use_corner_band ? vreg->corner_band[num].sdelta :
 | |
| +		vreg->corner[num].sdelta;
 | |
| +
 | |
| +	if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
 | |
| +		/* corner doesn't need sdelta table */
 | |
| +		sdelta->max_core_count = 0;
 | |
| +		sdelta->temp_band_count = 0;
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	sdelta_size = sdelta->max_core_count * sdelta->temp_band_count;
 | |
| +	if (use_corner_band)
 | |
| +		snprintf(str, sizeof(str),
 | |
| +			 "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
 | |
| +			 num, sdelta->max_core_count,
 | |
| +			 sdelta->temp_band_count, sdelta_size);
 | |
| +	else
 | |
| +		snprintf(str, sizeof(str),
 | |
| +			 "corner=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
 | |
| +			 num, sdelta->max_core_count,
 | |
| +			 sdelta->temp_band_count, sdelta_size);
 | |
| +
 | |
| +	cpr3_debug(vreg, "%s", str);
 | |
| +
 | |
| +	sdelta->table = devm_kcalloc(ctrl->dev, sdelta_size,
 | |
| +				sizeof(*sdelta->table), GFP_KERNEL);
 | |
| +	if (!sdelta->table)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	if (use_corner_band)
 | |
| +		snprintf(str, sizeof(str),
 | |
| +			 "qcom,cpr-corner-band%d-temp-core-voltage-adjustment",
 | |
| +			 num + CPR3_CORNER_OFFSET);
 | |
| +	else
 | |
| +		snprintf(str, sizeof(str),
 | |
| +			 "qcom,cpr-corner%d-temp-core-voltage-adjustment",
 | |
| +			 num + CPR3_CORNER_OFFSET);
 | |
| +
 | |
| +	rc = cpr3_parse_array_property(vreg, str, sdelta_size,
 | |
| +				sdelta->table);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load %s, rc=%d\n", str, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Convert sdelta margins from uV to PMIC steps and apply negation to
 | |
| +	 * follow the SDELTA register semantics.
 | |
| +	 */
 | |
| +	for (i = 0; i < sdelta_size; i++)
 | |
| +		sdelta->table[i] = -(sdelta->table[i] / ctrl->step_volt);
 | |
| +
 | |
| +	buflen = sizeof(*buf) * sdelta_size * (MAX_CHARS_PER_INT + 2);
 | |
| +	buf = kzalloc(buflen, GFP_KERNEL);
 | |
| +	if (!buf)
 | |
| +		return rc;
 | |
| +
 | |
| +	for (i = 0; i < sdelta->max_core_count; i++) {
 | |
| +		for (j = 0, pos = 0; j < sdelta->temp_band_count; j++)
 | |
| +			pos += scnprintf(buf + pos, buflen - pos, " %u",
 | |
| +			 sdelta->table[i * sdelta->temp_band_count + j]);
 | |
| +		cpr3_debug(vreg, "sdelta[%d]:%s\n", i, buf);
 | |
| +	}
 | |
| +
 | |
| +	kfree(buf);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_parse_core_count_temp_voltage_adj() - parse configuration data for
 | |
| + *		per-online-core and per-temperature voltage adjustment for
 | |
| + *		a CPR3 regulator from device tree.
 | |
| + * @vreg:	Pointer to the CPR3 regulator
 | |
| + * @use_corner_band:	Boolean indicating if the CPR3 regulator supports
 | |
| + *			adjustments per corner band
 | |
| + *
 | |
| + * This function supports parsing of per-online-core and per-temperature
 | |
| + * adjustments per corner or per corner band. CPR controllers which support
 | |
| + * corner bands apply the same adjustments to all corners within a corner band.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr4_parse_core_count_temp_voltage_adj(
 | |
| +			struct cpr3_regulator *vreg, bool use_corner_band)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	struct cpr3_corner *corner;
 | |
| +	struct cpr4_sdelta *sdelta;
 | |
| +	int i, sdelta_table_count, rc = 0;
 | |
| +	int *allow_core_count_adj = NULL, *allow_temp_adj = NULL;
 | |
| +	char prop_str[75];
 | |
| +
 | |
| +	if (of_find_property(node, use_corner_band ?
 | |
| +			     "qcom,corner-band-allow-temp-adjustment"
 | |
| +			     : "qcom,corner-allow-temp-adjustment", NULL)) {
 | |
| +		if (!ctrl->allow_temp_adj) {
 | |
| +			cpr3_err(ctrl, "Temperature adjustment configurations missing\n");
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		vreg->allow_temp_adj = true;
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(node, use_corner_band ?
 | |
| +			     "qcom,corner-band-allow-core-count-adjustment"
 | |
| +			     : "qcom,corner-allow-core-count-adjustment",
 | |
| +			     NULL)) {
 | |
| +		rc = of_property_read_u32(node, "qcom,max-core-count",
 | |
| +				&vreg->max_core_count);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading qcom,max-core-count, rc=%d\n",
 | |
| +				rc);
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +
 | |
| +		vreg->allow_core_count_adj = true;
 | |
| +		ctrl->allow_core_count_adj = true;
 | |
| +	}
 | |
| +
 | |
| +	if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) {
 | |
| +		/*
 | |
| +		 * Both per-online-core and temperature based adjustments are
 | |
| +		 * disabled for this regulator.
 | |
| +		 */
 | |
| +		return 0;
 | |
| +	} else if (!vreg->allow_core_count_adj) {
 | |
| +		/*
 | |
| +		 * Only per-temperature voltage adjusments are allowed.
 | |
| +		 * Keep max core count value as 1 to allocate SDELTA.
 | |
| +		 */
 | |
| +		vreg->max_core_count = 1;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->allow_core_count_adj) {
 | |
| +		allow_core_count_adj = kcalloc(vreg->corner_count,
 | |
| +					sizeof(*allow_core_count_adj),
 | |
| +					GFP_KERNEL);
 | |
| +		if (!allow_core_count_adj)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
 | |
| +			 "qcom,corner-band-allow-core-count-adjustment" :
 | |
| +			 "qcom,corner-allow-core-count-adjustment");
 | |
| +
 | |
| +		rc = use_corner_band ?
 | |
| +			cpr3_parse_corner_band_array_property(vreg, prop_str,
 | |
| +					      1, allow_core_count_adj) :
 | |
| +			cpr3_parse_corner_array_property(vreg, prop_str,
 | |
| +						 1, allow_core_count_adj);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
 | |
| +				 rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->allow_temp_adj) {
 | |
| +		allow_temp_adj = kcalloc(vreg->corner_count,
 | |
| +					sizeof(*allow_temp_adj), GFP_KERNEL);
 | |
| +		if (!allow_temp_adj) {
 | |
| +			rc = -ENOMEM;
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
 | |
| +			 "qcom,corner-band-allow-temp-adjustment" :
 | |
| +			 "qcom,corner-allow-temp-adjustment");
 | |
| +
 | |
| +		rc = use_corner_band ?
 | |
| +			cpr3_parse_corner_band_array_property(vreg, prop_str,
 | |
| +						      1, allow_temp_adj) :
 | |
| +			cpr3_parse_corner_array_property(vreg, prop_str,
 | |
| +						 1, allow_temp_adj);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
 | |
| +				 rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	sdelta_table_count = use_corner_band ? vreg->corner_band_count :
 | |
| +		vreg->corner_count;
 | |
| +
 | |
| +	for (i = 0; i < sdelta_table_count; i++) {
 | |
| +		sdelta = devm_kzalloc(ctrl->dev, sizeof(*corner->sdelta),
 | |
| +				      GFP_KERNEL);
 | |
| +		if (!sdelta) {
 | |
| +			rc = -ENOMEM;
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		if (allow_core_count_adj)
 | |
| +			sdelta->allow_core_count_adj = allow_core_count_adj[i];
 | |
| +		if (allow_temp_adj)
 | |
| +			sdelta->allow_temp_adj = allow_temp_adj[i];
 | |
| +		sdelta->max_core_count = vreg->max_core_count;
 | |
| +		sdelta->temp_band_count = ctrl->temp_band_count;
 | |
| +
 | |
| +		if (use_corner_band)
 | |
| +			vreg->corner_band[i].sdelta = sdelta;
 | |
| +		else
 | |
| +			vreg->corner[i].sdelta = sdelta;
 | |
| +
 | |
| +		rc = cpr4_load_core_and_temp_adj(vreg, i, use_corner_band);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "corner/band %d core and temp adjustment loading failed, rc=%d\n",
 | |
| +				 i, rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(allow_core_count_adj);
 | |
| +	kfree(allow_temp_adj);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cprh_adjust_voltages_for_apm() - adjust per-corner floor and ceiling voltages
 | |
| + *		so that they do not overlap the APM threshold voltage.
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * The memory array power mux (APM) must be configured for a specific supply
 | |
| + * based upon where the VDD voltage lies with respect to the APM threshold
 | |
| + * voltage.  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 every corner
 | |
| + * not intersect the APM threshold voltage.  This function adjusts the floor to
 | |
| + * ceiling range for each corner which violates this requirement.
 | |
| + *
 | |
| + * The following algorithm is applied:
 | |
| + *	if floor < threshold <= ceiling:
 | |
| + *		if open_loop >= threshold, then floor = threshold - adj
 | |
| + *		else ceiling = threshold - step
 | |
| + * where:
 | |
| + *	adj = APM hysteresis voltage established to minimize the number of
 | |
| + *	      corners with artificially increased floor voltages
 | |
| + *	step = voltage in microvolts of a single step of the VDD supply
 | |
| + *
 | |
| + * The open-loop voltage is also bounded by the new floor or ceiling value as
 | |
| + * needed.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	struct cpr3_corner *corner;
 | |
| +	int i, adj, threshold, prev_ceiling, prev_floor, prev_open_loop;
 | |
| +
 | |
| +	if (!ctrl->apm_threshold_volt) {
 | |
| +		/* APM not being used. */
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->apm_threshold_volt = CPR3_ROUND(ctrl->apm_threshold_volt,
 | |
| +						ctrl->step_volt);
 | |
| +	ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
 | |
| +
 | |
| +	threshold = ctrl->apm_threshold_volt;
 | |
| +	adj = ctrl->apm_adj_volt;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		corner = &vreg->corner[i];
 | |
| +
 | |
| +		if (threshold <= corner->floor_volt
 | |
| +		    || threshold > corner->ceiling_volt)
 | |
| +			continue;
 | |
| +
 | |
| +		prev_floor = corner->floor_volt;
 | |
| +		prev_ceiling = corner->ceiling_volt;
 | |
| +		prev_open_loop = corner->open_loop_volt;
 | |
| +
 | |
| +		if (corner->open_loop_volt >= threshold) {
 | |
| +			corner->floor_volt = max(corner->floor_volt,
 | |
| +						 threshold - adj);
 | |
| +			if (corner->open_loop_volt < corner->floor_volt)
 | |
| +				corner->open_loop_volt = corner->floor_volt;
 | |
| +		} else {
 | |
| +			corner->ceiling_volt = threshold - ctrl->step_volt;
 | |
| +		}
 | |
| +
 | |
| +		if (corner->floor_volt != prev_floor
 | |
| +		    || corner->ceiling_volt != prev_ceiling
 | |
| +		    || corner->open_loop_volt != prev_open_loop)
 | |
| +			cpr3_debug(vreg, "APM threshold=%d, APM adj=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
 | |
| +				threshold, adj, i, prev_floor, prev_ceiling,
 | |
| +				prev_open_loop, corner->floor_volt,
 | |
| +				corner->ceiling_volt, corner->open_loop_volt);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cprh_adjust_voltages_for_mem_acc() - adjust per-corner floor and ceiling
 | |
| + *		voltages so that they do not intersect the MEM ACC threshold
 | |
| + *		voltage
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * The following algorithm is applied:
 | |
| + *	if floor < threshold <= ceiling:
 | |
| + *		if open_loop >= threshold, then floor = threshold
 | |
| + *		else ceiling = threshold - step
 | |
| + * where:
 | |
| + *	step = voltage in microvolts of a single step of the VDD supply
 | |
| + *
 | |
| + * The open-loop voltage is also bounded by the new floor or ceiling value as
 | |
| + * needed.
 | |
| + *
 | |
| + * Return: none
 | |
| + */
 | |
| +void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	struct cpr3_corner *corner;
 | |
| +	int i, threshold, prev_ceiling, prev_floor, prev_open_loop;
 | |
| +
 | |
| +	if (!ctrl->mem_acc_threshold_volt) {
 | |
| +		/* MEM ACC not being used. */
 | |
| +		return;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->mem_acc_threshold_volt = CPR3_ROUND(ctrl->mem_acc_threshold_volt,
 | |
| +						ctrl->step_volt);
 | |
| +
 | |
| +	threshold = ctrl->mem_acc_threshold_volt;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		corner = &vreg->corner[i];
 | |
| +
 | |
| +		if (threshold <= corner->floor_volt
 | |
| +		    || threshold > corner->ceiling_volt)
 | |
| +			continue;
 | |
| +
 | |
| +		prev_floor = corner->floor_volt;
 | |
| +		prev_ceiling = corner->ceiling_volt;
 | |
| +		prev_open_loop = corner->open_loop_volt;
 | |
| +
 | |
| +		if (corner->open_loop_volt >= threshold) {
 | |
| +			corner->floor_volt = max(corner->floor_volt, threshold);
 | |
| +			if (corner->open_loop_volt < corner->floor_volt)
 | |
| +				corner->open_loop_volt = corner->floor_volt;
 | |
| +		} else {
 | |
| +			corner->ceiling_volt = threshold - ctrl->step_volt;
 | |
| +		}
 | |
| +
 | |
| +		if (corner->floor_volt != prev_floor
 | |
| +		    || corner->ceiling_volt != prev_ceiling
 | |
| +		    || corner->open_loop_volt != prev_open_loop)
 | |
| +			cpr3_debug(vreg, "MEM ACC threshold=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
 | |
| +				threshold, i, prev_floor, prev_ceiling,
 | |
| +				prev_open_loop, corner->floor_volt,
 | |
| +				corner->ceiling_volt, corner->open_loop_volt);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_apply_closed_loop_offset_voltages() - modify the closed-loop voltage
 | |
| + *		adjustments by the amounts that are needed for this
 | |
| + *		fuse combo
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @volt_adjust:	Array of closed-loop voltage adjustment values of length
 | |
| + *			vreg->corner_count which is further adjusted based upon
 | |
| + *			offset voltage fuse values.
 | |
| + * @fuse_volt_adjust:	Fused closed-loop voltage adjustment values of length
 | |
| + *			vreg->fuse_corner_count.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr3_apply_closed_loop_offset_voltages(struct cpr3_regulator *vreg,
 | |
| +			int *volt_adjust, int *fuse_volt_adjust)
 | |
| +{
 | |
| +	u32 *corner_map;
 | |
| +	int rc = 0, i;
 | |
| +
 | |
| +	if (!of_find_property(vreg->of_node,
 | |
| +		"qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL)) {
 | |
| +		/* No closed-loop offset required. */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	corner_map = kcalloc(vreg->corner_count, sizeof(*corner_map),
 | |
| +				GFP_KERNEL);
 | |
| +	if (!corner_map)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg,
 | |
| +		"qcom,cpr-fused-closed-loop-voltage-adjustment-map",
 | |
| +		1, corner_map);
 | |
| +	if (rc)
 | |
| +		goto done;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		if (corner_map[i] == 0) {
 | |
| +			continue;
 | |
| +		} else if (corner_map[i] > vreg->fuse_corner_count) {
 | |
| +			cpr3_err(vreg, "corner %d mapped to invalid fuse corner: %u\n",
 | |
| +				i, corner_map[i]);
 | |
| +			rc = -EINVAL;
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		volt_adjust[i] += fuse_volt_adjust[corner_map[i] - 1];
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(corner_map);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_enforce_inc_quotient_monotonicity() - Ensure that target quotients
 | |
| + *		increase monotonically from lower to higher corners
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static void cpr3_enforce_inc_quotient_monotonicity(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i, j;
 | |
| +
 | |
| +	for (i = 1; i < vreg->corner_count; i++) {
 | |
| +		for (j = 0; j < CPR3_RO_COUNT; j++) {
 | |
| +			if (vreg->corner[i].target_quot[j]
 | |
| +			    && vreg->corner[i].target_quot[j]
 | |
| +					< vreg->corner[i - 1].target_quot[j]) {
 | |
| +				cpr3_debug(vreg, "corner %d RO%u target quot=%u < corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
 | |
| +					i, j,
 | |
| +					vreg->corner[i].target_quot[j],
 | |
| +					i - 1, j,
 | |
| +					vreg->corner[i - 1].target_quot[j],
 | |
| +					i, j,
 | |
| +					vreg->corner[i - 1].target_quot[j]);
 | |
| +				vreg->corner[i].target_quot[j]
 | |
| +					= vreg->corner[i - 1].target_quot[j];
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_enforce_dec_quotient_monotonicity() - Ensure that target quotients
 | |
| + *		decrease monotonically from higher to lower corners
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static void cpr3_enforce_dec_quotient_monotonicity(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	int i, j;
 | |
| +
 | |
| +	for (i = vreg->corner_count - 2; i >= 0; i--) {
 | |
| +		for (j = 0; j < CPR3_RO_COUNT; j++) {
 | |
| +			if (vreg->corner[i + 1].target_quot[j]
 | |
| +			    && vreg->corner[i].target_quot[j]
 | |
| +					> vreg->corner[i + 1].target_quot[j]) {
 | |
| +				cpr3_debug(vreg, "corner %d RO%u target quot=%u > corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
 | |
| +					i, j,
 | |
| +					vreg->corner[i].target_quot[j],
 | |
| +					i + 1, j,
 | |
| +					vreg->corner[i + 1].target_quot[j],
 | |
| +					i, j,
 | |
| +					vreg->corner[i + 1].target_quot[j]);
 | |
| +				vreg->corner[i].target_quot[j]
 | |
| +					= vreg->corner[i + 1].target_quot[j];
 | |
| +			}
 | |
| +		}
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * _cpr3_adjust_target_quotients() - adjust the target quotients for each
 | |
| + *		corner of the regulator according to input adjustment and
 | |
| + *		scaling arrays
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @volt_adjust:	Pointer to an array of closed-loop voltage adjustments
 | |
| + *			with units of microvolts.  The array must have
 | |
| + *			vreg->corner_count number of elements.
 | |
| + * @ro_scale:		Pointer to a flattened 2D array of RO scaling factors.
 | |
| + *			The array must have an inner dimension of CPR3_RO_COUNT
 | |
| + *			and an outer dimension of vreg->corner_count
 | |
| + * @label:		Null terminated string providing a label for the type
 | |
| + *			of adjustment.
 | |
| + *
 | |
| + * Return: true if any corners received a positive voltage adjustment (> 0),
 | |
| + *	   else false
 | |
| + */
 | |
| +static bool _cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
 | |
| +		const int *volt_adjust, const int *ro_scale, const char *label)
 | |
| +{
 | |
| +	int i, j, quot_adjust;
 | |
| +	bool is_increasing = false;
 | |
| +	u32 prev_quot;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		for (j = 0; j < CPR3_RO_COUNT; j++) {
 | |
| +			if (vreg->corner[i].target_quot[j]) {
 | |
| +				quot_adjust = cpr3_quot_adjustment(
 | |
| +					ro_scale[i * CPR3_RO_COUNT + j],
 | |
| +					volt_adjust[i]);
 | |
| +				if (quot_adjust) {
 | |
| +					prev_quot = vreg->corner[i].
 | |
| +							target_quot[j];
 | |
| +					vreg->corner[i].target_quot[j]
 | |
| +						+= quot_adjust;
 | |
| +					cpr3_debug(vreg, "adjusted corner %d RO%d target quot %s: %u --> %u (%d uV)\n",
 | |
| +						i, j, label, prev_quot,
 | |
| +						vreg->corner[i].target_quot[j],
 | |
| +						volt_adjust[i]);
 | |
| +				}
 | |
| +			}
 | |
| +		}
 | |
| +		if (volt_adjust[i] > 0)
 | |
| +			is_increasing = true;
 | |
| +	}
 | |
| +
 | |
| +	return is_increasing;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr3_adjust_target_quotients() - adjust the target quotients for each
 | |
| + *			corner according to device tree values and fuse values
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @fuse_volt_adjust:	Fused closed-loop voltage adjustment values of length
 | |
| + *			vreg->fuse_corner_count. This parameter could be null
 | |
| + *			pointer when no fused adjustments are needed.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
 | |
| +			int *fuse_volt_adjust)
 | |
| +{
 | |
| +	int i, rc;
 | |
| +	int *volt_adjust, *ro_scale;
 | |
| +	bool explicit_adjustment, fused_adjustment, is_increasing;
 | |
| +
 | |
| +	explicit_adjustment = of_find_property(vreg->of_node,
 | |
| +		"qcom,cpr-closed-loop-voltage-adjustment", NULL);
 | |
| +	fused_adjustment = of_find_property(vreg->of_node,
 | |
| +		"qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL);
 | |
| +
 | |
| +	if (!explicit_adjustment && !fused_adjustment && !vreg->aging_allowed) {
 | |
| +		/* No adjustment required. */
 | |
| +		return 0;
 | |
| +	} else if (!of_find_property(vreg->of_node,
 | |
| +			"qcom,cpr-ro-scaling-factor", NULL)) {
 | |
| +		cpr3_err(vreg, "qcom,cpr-ro-scaling-factor is required for closed-loop voltage adjustment, but is missing\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
 | |
| +				GFP_KERNEL);
 | |
| +	ro_scale = kcalloc(vreg->corner_count * CPR3_RO_COUNT,
 | |
| +				sizeof(*ro_scale), GFP_KERNEL);
 | |
| +	if (!volt_adjust || !ro_scale) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_corner_array_property(vreg,
 | |
| +			"qcom,cpr-ro-scaling-factor", CPR3_RO_COUNT, ro_scale);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++)
 | |
| +		memcpy(vreg->corner[i].ro_scale, &ro_scale[i * CPR3_RO_COUNT],
 | |
| +			sizeof(*ro_scale) * CPR3_RO_COUNT);
 | |
| +
 | |
| +	if (explicit_adjustment) {
 | |
| +		rc = cpr3_parse_corner_array_property(vreg,
 | |
| +			"qcom,cpr-closed-loop-voltage-adjustment",
 | |
| +			1, volt_adjust);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		_cpr3_adjust_target_quotients(vreg, volt_adjust, ro_scale,
 | |
| +			"from DT");
 | |
| +		cpr3_enforce_inc_quotient_monotonicity(vreg);
 | |
| +	}
 | |
| +
 | |
| +	if (fused_adjustment && fuse_volt_adjust) {
 | |
| +		memset(volt_adjust, 0,
 | |
| +			sizeof(*volt_adjust) * vreg->corner_count);
 | |
| +
 | |
| +		rc = cpr3_apply_closed_loop_offset_voltages(vreg, volt_adjust,
 | |
| +				fuse_volt_adjust);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "could not apply fused closed-loop voltage reductions, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		is_increasing = _cpr3_adjust_target_quotients(vreg, volt_adjust,
 | |
| +					ro_scale, "from fuse");
 | |
| +		if (is_increasing)
 | |
| +			cpr3_enforce_inc_quotient_monotonicity(vreg);
 | |
| +		else
 | |
| +			cpr3_enforce_dec_quotient_monotonicity(vreg);
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(volt_adjust);
 | |
| +	kfree(ro_scale);
 | |
| +	return rc;
 | |
| +}
 | |
| --- /dev/null
 | |
| +++ b/drivers/regulator/cpr4-apss-regulator.c
 | |
| @@ -0,0 +1,1819 @@
 | |
| +/*
 | |
| + * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 and
 | |
| + * only version 2 as published by the Free Software Foundation.
 | |
| + *
 | |
| + * This program is distributed in the hope that it will be useful,
 | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| + * GNU General Public License for more details.
 | |
| + */
 | |
| +
 | |
| +#define pr_fmt(fmt) "%s: " fmt, __func__
 | |
| +
 | |
| +#include <linux/bitops.h>
 | |
| +#include <linux/debugfs.h>
 | |
| +#include <linux/err.h>
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/interrupt.h>
 | |
| +#include <linux/io.h>
 | |
| +#include <linux/kernel.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/string.h>
 | |
| +#include <linux/uaccess.h>
 | |
| +#include <linux/regulator/driver.h>
 | |
| +#include <linux/regulator/machine.h>
 | |
| +#include <linux/regulator/of_regulator.h>
 | |
| +
 | |
| +#include "cpr3-regulator.h"
 | |
| +
 | |
| +#define IPQ807x_APSS_FUSE_CORNERS	4
 | |
| +#define IPQ817x_APPS_FUSE_CORNERS	2
 | |
| +#define IPQ6018_APSS_FUSE_CORNERS 	4
 | |
| +#define IPQ9574_APSS_FUSE_CORNERS       4
 | |
| +
 | |
| +u32 g_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS;
 | |
| +
 | |
| +/**
 | |
| + * struct cpr4_ipq807x_apss_fuses - APSS specific fuse data for IPQ807x
 | |
| + * @ro_sel:		Ring oscillator select fuse parameter value for each
 | |
| + *			fuse corner
 | |
| + * @init_voltage:	Initial (i.e. open-loop) voltage fuse parameter value
 | |
| + *			for each fuse corner (raw, not converted to a voltage)
 | |
| + * @target_quot:	CPR target quotient fuse parameter value for each fuse
 | |
| + *			corner
 | |
| + * @quot_offset:	CPR target quotient offset fuse parameter value for each
 | |
| + *			fuse corner (raw, not unpacked) used for target quotient
 | |
| + *			interpolation
 | |
| + * @speed_bin:		Application processor speed bin fuse parameter value for
 | |
| + *			the given chip
 | |
| + * @cpr_fusing_rev:	CPR fusing revision fuse parameter value
 | |
| + * @boost_cfg:		CPR boost configuration fuse parameter value
 | |
| + * @boost_voltage:	CPR boost voltage fuse parameter value (raw, not
 | |
| + *			converted to a voltage)
 | |
| + *
 | |
| + * This struct holds the values for all of the fuses read from memory.
 | |
| + */
 | |
| +struct cpr4_ipq807x_apss_fuses {
 | |
| +	u64	ro_sel[IPQ807x_APSS_FUSE_CORNERS];
 | |
| +	u64	init_voltage[IPQ807x_APSS_FUSE_CORNERS];
 | |
| +	u64	target_quot[IPQ807x_APSS_FUSE_CORNERS];
 | |
| +	u64	quot_offset[IPQ807x_APSS_FUSE_CORNERS];
 | |
| +	u64	speed_bin;
 | |
| +	u64	cpr_fusing_rev;
 | |
| +	u64	boost_cfg;
 | |
| +	u64	boost_voltage;
 | |
| +	u64	misc;
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * fuse combo = fusing revision + 8 * (speed bin)
 | |
| + * where: fusing revision = 0 - 7 and speed bin = 0 - 7
 | |
| + */
 | |
| +#define CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT	64
 | |
| +
 | |
| +/*
 | |
| + * Constants which define the name of each fuse corner.
 | |
| + */
 | |
| +enum cpr4_ipq807x_apss_fuse_corner {
 | |
| +	CPR4_IPQ807x_APSS_FUSE_CORNER_SVS	= 0,
 | |
| +	CPR4_IPQ807x_APSS_FUSE_CORNER_NOM	= 1,
 | |
| +	CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO	= 2,
 | |
| +	CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO	= 3,
 | |
| +};
 | |
| +
 | |
| +static const char * const cpr4_ipq807x_apss_fuse_corner_name[] = {
 | |
| +	[CPR4_IPQ807x_APSS_FUSE_CORNER_SVS]	= "SVS",
 | |
| +	[CPR4_IPQ807x_APSS_FUSE_CORNER_NOM]	= "NOM",
 | |
| +	[CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO]	= "TURBO",
 | |
| +	[CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO]	= "STURBO",
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * IPQ807x APSS fuse parameter locations:
 | |
| + *
 | |
| + * Structs are organized with the following dimensions:
 | |
| + *	Outer: 0 to 3 for fuse corners from lowest to highest corner
 | |
| + *	Inner: large enough to hold the longest set of parameter segments which
 | |
| + *		fully defines a fuse parameter, +1 (for NULL termination).
 | |
| + *		Each segment corresponds to a contiguous group of bits from a
 | |
| + *		single fuse row.  These segments are concatentated together in
 | |
| + *		order to form the full fuse parameter value.  The segments for
 | |
| + *		a given parameter may correspond to different fuse rows.
 | |
| + */
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq807x_apss_ro_sel_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{73,  8, 11}, {} },
 | |
| +	{{73,  4,  7}, {} },
 | |
| +	{{73,  0,  3}, {} },
 | |
| +	{{73, 12, 15}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq807x_apss_init_voltage_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{71, 18, 23}, {} },
 | |
| +	{{71, 12, 17}, {} },
 | |
| +	{{71,  6, 11}, {} },
 | |
| +	{{71,  0,  5}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq807x_apss_target_quot_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{72, 32, 43}, {} },
 | |
| +	{{72, 20, 31}, {} },
 | |
| +	{{72,  8, 19}, {} },
 | |
| +	{{72, 44, 55}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq807x_apss_quot_offset_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{} },
 | |
| +	{{71, 46, 52}, {} },
 | |
| +	{{71, 39, 45}, {} },
 | |
| +	{{71, 32, 38}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq807x_cpr_fusing_rev_param[] = {
 | |
| +	{71, 53, 55},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq807x_apss_speed_bin_param[] = {
 | |
| +	{36, 40, 42},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq807x_cpr_boost_fuse_cfg_param[] = {
 | |
| +	{36, 43, 45},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq807x_apss_boost_fuse_volt_param[] = {
 | |
| +	{71, 0, 5},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq807x_misc_fuse_volt_adj_param[] = {
 | |
| +	{36, 54, 54},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_parameters ipq807x_fuse_params = {
 | |
| +	.apss_ro_sel_param = ipq807x_apss_ro_sel_param,
 | |
| +	.apss_init_voltage_param = ipq807x_apss_init_voltage_param,
 | |
| +	.apss_target_quot_param = ipq807x_apss_target_quot_param,
 | |
| +	.apss_quot_offset_param = ipq807x_apss_quot_offset_param,
 | |
| +	.cpr_fusing_rev_param = ipq807x_cpr_fusing_rev_param,
 | |
| +	.apss_speed_bin_param = ipq807x_apss_speed_bin_param,
 | |
| +	.cpr_boost_fuse_cfg_param = ipq807x_cpr_boost_fuse_cfg_param,
 | |
| +	.apss_boost_fuse_volt_param = ipq807x_apss_boost_fuse_volt_param,
 | |
| +	.misc_fuse_volt_adj_param = ipq807x_misc_fuse_volt_adj_param
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * The number of possible values for misc fuse is
 | |
| + * 2^(#bits defined for misc fuse)
 | |
| + */
 | |
| +#define IPQ807x_MISC_FUSE_VAL_COUNT		BIT(1)
 | |
| +
 | |
| +/*
 | |
| + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
 | |
| + */
 | |
| +static int ipq807x_apss_fuse_ref_volt
 | |
| +	[IPQ807x_APSS_FUSE_CORNERS] = {
 | |
| +	720000,
 | |
| +	864000,
 | |
| +	992000,
 | |
| +	1064000,
 | |
| +};
 | |
| +
 | |
| +#define IPQ807x_APSS_FUSE_STEP_VOLT		8000
 | |
| +#define IPQ807x_APSS_VOLTAGE_FUSE_SIZE	6
 | |
| +#define IPQ807x_APSS_QUOT_OFFSET_SCALE	5
 | |
| +
 | |
| +#define IPQ807x_APSS_CPR_SENSOR_COUNT	6
 | |
| +
 | |
| +#define IPQ807x_APSS_CPR_CLOCK_RATE		19200000
 | |
| +
 | |
| +#define IPQ807x_APSS_MAX_TEMP_POINTS	3
 | |
| +#define IPQ807x_APSS_TEMP_SENSOR_ID_START	4
 | |
| +#define IPQ807x_APSS_TEMP_SENSOR_ID_END	13
 | |
| +/*
 | |
| + * Boost voltage fuse reference and ceiling voltages in microvolts for
 | |
| + * IPQ807x.
 | |
| + */
 | |
| +#define IPQ807x_APSS_BOOST_FUSE_REF_VOLT	1140000
 | |
| +#define IPQ807x_APSS_BOOST_CEILING_VOLT	1140000
 | |
| +#define IPQ807x_APSS_BOOST_FLOOR_VOLT	900000
 | |
| +#define MAX_BOOST_CONFIG_FUSE_VALUE		8
 | |
| +
 | |
| +#define IPQ807x_APSS_CPR_SDELTA_CORE_COUNT	15
 | |
| +
 | |
| +#define IPQ807x_APSS_CPR_TCSR_START		8
 | |
| +#define IPQ807x_APSS_CPR_TCSR_END		9
 | |
| +
 | |
| +/*
 | |
| + * Array of integer values mapped to each of the boost config fuse values to
 | |
| + * indicate boost enable/disable status.
 | |
| + */
 | |
| +static bool boost_fuse[MAX_BOOST_CONFIG_FUSE_VALUE] = {0, 1, 1, 1, 1, 1, 1, 1};
 | |
| +
 | |
| +/*
 | |
| + * IPQ6018 (Few parameters are changed, remaining are same as IPQ807x)
 | |
| + */
 | |
| +#define IPQ6018_APSS_FUSE_STEP_VOLT		12500
 | |
| +#define IPQ6018_APSS_CPR_CLOCK_RATE		24000000
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq6018_apss_ro_sel_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{75,  8, 11}, {} },
 | |
| +	{{75,  4,  7}, {} },
 | |
| +	{{75,  0,  3}, {} },
 | |
| +	{{75, 12, 15}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq6018_apss_init_voltage_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{73, 18, 23}, {} },
 | |
| +	{{73, 12, 17}, {} },
 | |
| +	{{73,  6, 11}, {} },
 | |
| +	{{73,  0,  5}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq6018_apss_target_quot_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{74, 32, 43}, {} },
 | |
| +	{{74, 20, 31}, {} },
 | |
| +	{{74,  8, 19}, {} },
 | |
| +	{{74, 44, 55}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq6018_apss_quot_offset_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{} },
 | |
| +	{{73, 48, 55}, {} },
 | |
| +	{{73, 40, 47}, {} },
 | |
| +	{{73, 32, 39}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq6018_cpr_fusing_rev_param[] = {
 | |
| +	{75, 16, 18},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq6018_apss_speed_bin_param[] = {
 | |
| +	{36, 40, 42},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq6018_cpr_boost_fuse_cfg_param[] = {
 | |
| +	{36, 43, 45},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq6018_apss_boost_fuse_volt_param[] = {
 | |
| +	{73, 0, 5},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq6018_misc_fuse_volt_adj_param[] = {
 | |
| +	{36, 54, 54},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_parameters ipq6018_fuse_params = {
 | |
| +	.apss_ro_sel_param = ipq6018_apss_ro_sel_param,
 | |
| +	.apss_init_voltage_param = ipq6018_apss_init_voltage_param,
 | |
| +	.apss_target_quot_param = ipq6018_apss_target_quot_param,
 | |
| +	.apss_quot_offset_param = ipq6018_apss_quot_offset_param,
 | |
| +	.cpr_fusing_rev_param = ipq6018_cpr_fusing_rev_param,
 | |
| +	.apss_speed_bin_param = ipq6018_apss_speed_bin_param,
 | |
| +	.cpr_boost_fuse_cfg_param = ipq6018_cpr_boost_fuse_cfg_param,
 | |
| +	.apss_boost_fuse_volt_param = ipq6018_apss_boost_fuse_volt_param,
 | |
| +	.misc_fuse_volt_adj_param = ipq6018_misc_fuse_volt_adj_param
 | |
| +};
 | |
| +
 | |
| +
 | |
| +/*
 | |
| + * Boost voltage fuse reference and ceiling voltages in microvolts for
 | |
| + * IPQ6018.
 | |
| + */
 | |
| +#define IPQ6018_APSS_BOOST_FUSE_REF_VOLT	1140000
 | |
| +#define IPQ6018_APSS_BOOST_CEILING_VOLT	1140000
 | |
| +#define IPQ6018_APSS_BOOST_FLOOR_VOLT	900000
 | |
| +
 | |
| +/*
 | |
| + * Open loop voltage fuse reference voltages in microvolts for IPQ807x
 | |
| + */
 | |
| +static int ipq6018_apss_fuse_ref_volt
 | |
| +	[IPQ6018_APSS_FUSE_CORNERS] = {
 | |
| +	725000,
 | |
| +	862500,
 | |
| +	987500,
 | |
| +	1062500,
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * IPQ6018 Memory ACC settings on TCSR
 | |
| + *
 | |
| + * Turbo_L1: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x10
 | |
| + *           write TCSR_CUSTOM_VDDAPC0_ACC_1            0x1
 | |
| + * Other modes: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x0
 | |
| + *              write TCSR_CUSTOM_VDDAPC0_ACC_1            0x0
 | |
| + *
 | |
| + */
 | |
| +#define IPQ6018_APSS_MEM_ACC_TCSR_COUNT         2
 | |
| +#define TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0    0x1946178
 | |
| +#define TCSR_CUSTOM_VDDAPC0_ACC_1               0x1946124
 | |
| +
 | |
| +struct mem_acc_tcsr {
 | |
| +	u32 phy_addr;
 | |
| +	void __iomem *ioremap_addr;
 | |
| +	u32 value;
 | |
| +};
 | |
| +
 | |
| +static struct mem_acc_tcsr ipq6018_mem_acc_tcsr[IPQ6018_APSS_MEM_ACC_TCSR_COUNT] = {
 | |
| +	{TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0, NULL, 0x10},
 | |
| +	{TCSR_CUSTOM_VDDAPC0_ACC_1, NULL, 0x1},
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * IPQ9574 (Few parameters are changed, remaining are same as IPQ6018)
 | |
| + */
 | |
| +#define IPQ9574_APSS_FUSE_STEP_VOLT             10000
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq9574_apss_ro_sel_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{107, 4, 7}, {} },
 | |
| +	{{107, 0, 3}, {} },
 | |
| +	{{106, 4, 7}, {} },
 | |
| +	{{106, 0, 3}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq9574_apss_init_voltage_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{104, 24, 29}, {} },
 | |
| +	{{104, 18, 23}, {} },
 | |
| +	{{104, 12, 17}, {} },
 | |
| +	{{104,  6, 11}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq9574_apss_target_quot_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{106, 32, 43}, {} },
 | |
| +	{{106, 20, 31}, {} },
 | |
| +	{{106,  8, 19}, {} },
 | |
| +	{{106, 44, 55}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param
 | |
| +ipq9574_apss_quot_offset_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
 | |
| +	{{} },
 | |
| +	{{105, 48, 55}, {} },
 | |
| +	{{105, 40, 47}, {} },
 | |
| +	{{105, 32, 39}, {} },
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq9574_cpr_fusing_rev_param[] = {
 | |
| +	{107, 8, 10},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq9574_apss_speed_bin_param[] = {
 | |
| +	{0, 40, 42},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq9574_cpr_boost_fuse_cfg_param[] = {
 | |
| +	{0, 43, 45},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq9574_apss_boost_fuse_volt_param[] = {
 | |
| +	{104, 0, 5},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_param ipq9574_misc_fuse_volt_adj_param[] = {
 | |
| +	{0, 54, 54},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +static struct cpr3_fuse_parameters ipq9574_fuse_params = {
 | |
| +	.apss_ro_sel_param = ipq9574_apss_ro_sel_param,
 | |
| +	.apss_init_voltage_param = ipq9574_apss_init_voltage_param,
 | |
| +	.apss_target_quot_param = ipq9574_apss_target_quot_param,
 | |
| +	.apss_quot_offset_param = ipq9574_apss_quot_offset_param,
 | |
| +	.cpr_fusing_rev_param = ipq9574_cpr_fusing_rev_param,
 | |
| +	.apss_speed_bin_param = ipq9574_apss_speed_bin_param,
 | |
| +	.cpr_boost_fuse_cfg_param = ipq9574_cpr_boost_fuse_cfg_param,
 | |
| +	.apss_boost_fuse_volt_param = ipq9574_apss_boost_fuse_volt_param,
 | |
| +	.misc_fuse_volt_adj_param = ipq9574_misc_fuse_volt_adj_param
 | |
| +};
 | |
| +
 | |
| +/*
 | |
| + * Open loop voltage fuse reference voltages in microvolts for IPQ9574
 | |
| + */
 | |
| +static int ipq9574_apss_fuse_ref_volt
 | |
| +	[IPQ9574_APSS_FUSE_CORNERS] = {
 | |
| +	725000,
 | |
| +	862500,
 | |
| +	987500,
 | |
| +	1062500,
 | |
| +};
 | |
| +
 | |
| +/**
 | |
| + * cpr4_ipq807x_apss_read_fuse_data() - load APSS specific fuse parameter values
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * This function allocates a cpr4_ipq807x_apss_fuses struct, fills it with
 | |
| + * values read out of hardware fuses, and finally copies common fuse values
 | |
| + * into the CPR3 regulator struct.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_ipq807x_apss_read_fuse_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	void __iomem *base = vreg->thread->ctrl->fuse_base;
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
 | |
| +	if (!fuse)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->apss_speed_bin_param,
 | |
| +				  &fuse->speed_bin);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "Unable to read speed bin fuse, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	cpr3_info(vreg, "speed bin = %llu\n", fuse->speed_bin);
 | |
| +
 | |
| +	rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_fusing_rev_param,
 | |
| +				  &fuse->cpr_fusing_rev);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "Unable to read CPR fusing revision fuse, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	cpr3_info(vreg, "CPR fusing revision = %llu\n", fuse->cpr_fusing_rev);
 | |
| +
 | |
| +	rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->misc_fuse_volt_adj_param,
 | |
| +				  &fuse->misc);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "Unable to read misc voltage adjustment fuse, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	cpr3_info(vreg, "CPR misc fuse value = %llu\n", fuse->misc);
 | |
| +	if (fuse->misc >= IPQ807x_MISC_FUSE_VAL_COUNT) {
 | |
| +		cpr3_err(vreg, "CPR misc fuse value = %llu, should be < %lu\n",
 | |
| +			fuse->misc, IPQ807x_MISC_FUSE_VAL_COUNT);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < g_valid_fuse_count; i++) {
 | |
| +		rc = cpr3_read_fuse_param(base,
 | |
| +				vreg->cpr4_regulator_data->cpr3_fuse_params->apss_init_voltage_param[i],
 | |
| +				&fuse->init_voltage[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
 | |
| +				i, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr3_read_fuse_param(base,
 | |
| +				vreg->cpr4_regulator_data->cpr3_fuse_params->apss_target_quot_param[i],
 | |
| +				&fuse->target_quot[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "Unable to read fuse-corner %d target quotient fuse, rc=%d\n",
 | |
| +				i, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr3_read_fuse_param(base,
 | |
| +				vreg->cpr4_regulator_data->cpr3_fuse_params->apss_ro_sel_param[i],
 | |
| +				&fuse->ro_sel[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "Unable to read fuse-corner %d RO select fuse, rc=%d\n",
 | |
| +				i, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr3_read_fuse_param(base,
 | |
| +				vreg->cpr4_regulator_data->cpr3_fuse_params->apss_quot_offset_param[i],
 | |
| +				&fuse->quot_offset[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "Unable to read fuse-corner %d quotient offset fuse, rc=%d\n",
 | |
| +				i, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_boost_fuse_cfg_param,
 | |
| +				  &fuse->boost_cfg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "Unable to read CPR boost config fuse, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +	cpr3_info(vreg, "Voltage boost fuse config = %llu boost = %s\n",
 | |
| +			fuse->boost_cfg, boost_fuse[fuse->boost_cfg]
 | |
| +			? "enable" : "disable");
 | |
| +
 | |
| +	rc = cpr3_read_fuse_param(base,
 | |
| +				vreg->cpr4_regulator_data->cpr3_fuse_params->apss_boost_fuse_volt_param,
 | |
| +				&fuse->boost_voltage);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "failed to read boost fuse voltage, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
 | |
| +	if (vreg->fuse_combo >= CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT) {
 | |
| +		cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
 | |
| +			vreg->fuse_combo);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	vreg->speed_bin_fuse	= fuse->speed_bin;
 | |
| +	vreg->cpr_rev_fuse	= fuse->cpr_fusing_rev;
 | |
| +	vreg->fuse_corner_count	= g_valid_fuse_count;
 | |
| +	vreg->platform_fuses	= fuse;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_parse_corner_data() - parse APSS corner data from device tree
 | |
| + *		properties of the CPR3 regulator's device node
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_parse_corner_data(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	u32 *temp = NULL;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	rc = cpr3_parse_common_corner_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	/* If fuse has incorrect RO Select values and dtsi has "qcom,cpr-ro-sel"
 | |
| +	 * entry with RO select values other than zero, then dtsi values will
 | |
| +	 * be used.
 | |
| +	 */
 | |
| +	if (of_find_property(node, "qcom,cpr-ro-sel", NULL)) {
 | |
| +		temp = kcalloc(vreg->fuse_corner_count, sizeof(*temp),
 | |
| +				GFP_KERNEL);
 | |
| +		if (!temp)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		rc = cpr3_parse_array_property(vreg, "qcom,cpr-ro-sel",
 | |
| +				vreg->fuse_corner_count, temp);
 | |
| +		if (rc)
 | |
| +			goto done;
 | |
| +
 | |
| +		for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +			if (temp[i] != 0)
 | |
| +				fuse->ro_sel[i] = temp[i];
 | |
| +		}
 | |
| +	}
 | |
| +done:
 | |
| +	kfree(temp);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_parse_misc_fuse_voltage_adjustments() - fill an array from a
 | |
| + *		portion of the voltage adjustments specified based on
 | |
| + *		miscellaneous fuse bits.
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @volt_adjust:	Voltage adjustment output data array which must be
 | |
| + *			of size vreg->corner_count
 | |
| + *
 | |
| + * cpr3_parse_common_corner_data() must be called for vreg before this function
 | |
| + * is called so that speed bin size elements are initialized.
 | |
| + *
 | |
| + * Two formats are supported for the device tree property:
 | |
| + * 1. Length == tuple_list_size * vreg->corner_count
 | |
| + *	(reading begins at index 0)
 | |
| + * 2. Length == tuple_list_size * vreg->speed_bin_corner_sum
 | |
| + *	(reading begins at index tuple_list_size * vreg->speed_bin_offset)
 | |
| + *
 | |
| + * Here, tuple_list_size is the number of possible values for misc fuse.
 | |
| + * All other property lengths are treated as errors.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_parse_misc_fuse_voltage_adjustments(
 | |
| +	struct cpr3_regulator *vreg, u32 *volt_adjust)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	int tuple_list_size = IPQ807x_MISC_FUSE_VAL_COUNT;
 | |
| +	int i, offset, rc, len = 0;
 | |
| +	const char *prop_name = "qcom,cpr-misc-fuse-voltage-adjustment";
 | |
| +
 | |
| +	if (!of_find_property(node, prop_name, &len)) {
 | |
| +		cpr3_err(vreg, "property %s is missing\n", prop_name);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	if (len == tuple_list_size * vreg->corner_count * sizeof(u32)) {
 | |
| +		offset = 0;
 | |
| +	} else if (vreg->speed_bin_corner_sum > 0 &&
 | |
| +			len == tuple_list_size * vreg->speed_bin_corner_sum
 | |
| +			* sizeof(u32)) {
 | |
| +		offset = tuple_list_size * vreg->speed_bin_offset
 | |
| +			+ fuse->misc * vreg->corner_count;
 | |
| +	} else {
 | |
| +		if (vreg->speed_bin_corner_sum > 0)
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_list_size * vreg->corner_count
 | |
| +					* sizeof(u32),
 | |
| +				tuple_list_size * vreg->speed_bin_corner_sum
 | |
| +					* sizeof(u32));
 | |
| +		else
 | |
| +			cpr3_err(vreg, "property %s has invalid length=%d, should be %zu\n",
 | |
| +				prop_name, len,
 | |
| +				tuple_list_size * vreg->corner_count
 | |
| +				* sizeof(u32));
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		rc = of_property_read_u32_index(node, prop_name, offset + i,
 | |
| +						&volt_adjust[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "error reading property %s, rc=%d\n",
 | |
| +				prop_name, rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_ipq807x_apss_calculate_open_loop_voltages() - calculate the open-loop
 | |
| + *		voltage for each corner of a CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * If open-loop voltage interpolation is allowed in device tree, then
 | |
| + * this function calculates the open-loop voltage for a given corner using
 | |
| + * linear interpolation.  This interpolation is performed using the processor
 | |
| + * frequencies of the lower and higher Fmax corners along with their fused
 | |
| + * open-loop voltages.
 | |
| + *
 | |
| + * If open-loop voltage interpolation is not allowed, then this function uses
 | |
| + * the Fmax fused open-loop voltage for all of the corners associated with a
 | |
| + * given fuse corner.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_ipq807x_apss_calculate_open_loop_voltages(
 | |
| +			struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct device_node *node = vreg->of_node;
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	int i, j, rc = 0;
 | |
| +	bool allow_interpolation;
 | |
| +	u64 freq_low, volt_low, freq_high, volt_high;
 | |
| +	int *fuse_volt, *misc_adj_volt;
 | |
| +	int *fmax_corner;
 | |
| +
 | |
| +	fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
 | |
| +				GFP_KERNEL);
 | |
| +	fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
 | |
| +				GFP_KERNEL);
 | |
| +	if (!fuse_volt || !fmax_corner) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (ctrl->cpr_global_setting == CPR_DISABLED)
 | |
| +			fuse_volt[i] = vreg->cpr4_regulator_data->fuse_ref_volt[i];
 | |
| +		else
 | |
| +			fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
 | |
| +				vreg->cpr4_regulator_data->fuse_ref_volt[i],
 | |
| +				vreg->cpr4_regulator_data->fuse_step_volt,
 | |
| +				fuse->init_voltage[i],
 | |
| +				IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
 | |
| +
 | |
| +		/* Log fused open-loop voltage values for debugging purposes. */
 | |
| +		cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
 | |
| +			  cpr4_ipq807x_apss_fuse_corner_name[i],
 | |
| +			  fuse_volt[i]);
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_determine_part_type(vreg,
 | |
| +			  fuse_volt[vreg->fuse_corner_count - 1]);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "fused part type detection failed failed, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "fused open-loop voltage adjustment failed, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	allow_interpolation = of_property_read_bool(node,
 | |
| +				"qcom,allow-voltage-interpolation");
 | |
| +
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		if (fuse_volt[i] < fuse_volt[i - 1]) {
 | |
| +			cpr3_info(vreg, "fuse corner %d voltage=%d uV < fuse corner %d voltage=%d uV; overriding: fuse corner %d voltage=%d\n",
 | |
| +				i, fuse_volt[i], i - 1, fuse_volt[i - 1],
 | |
| +				i, fuse_volt[i - 1]);
 | |
| +			fuse_volt[i] = fuse_volt[i - 1];
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (!allow_interpolation) {
 | |
| +		/* Use fused open-loop voltage for lower frequencies. */
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			vreg->corner[i].open_loop_volt
 | |
| +				= fuse_volt[vreg->corner[i].cpr_fuse_corner];
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	/* Determine highest corner mapped to each fuse corner */
 | |
| +	j = vreg->fuse_corner_count - 1;
 | |
| +	for (i = vreg->corner_count - 1; i >= 0; i--) {
 | |
| +		if (vreg->corner[i].cpr_fuse_corner == j) {
 | |
| +			fmax_corner[j] = i;
 | |
| +			j--;
 | |
| +		}
 | |
| +	}
 | |
| +	if (j >= 0) {
 | |
| +		cpr3_err(vreg, "invalid fuse corner mapping\n");
 | |
| +		rc = -EINVAL;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Interpolation is not possible for corners mapped to the lowest fuse
 | |
| +	 * corner so use the fuse corner value directly.
 | |
| +	 */
 | |
| +	for (i = 0; i <= fmax_corner[0]; i++)
 | |
| +		vreg->corner[i].open_loop_volt = fuse_volt[0];
 | |
| +
 | |
| +	/* Interpolate voltages for the higher fuse corners. */
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
 | |
| +		volt_low = fuse_volt[i - 1];
 | |
| +		freq_high = vreg->corner[fmax_corner[i]].proc_freq;
 | |
| +		volt_high = fuse_volt[i];
 | |
| +
 | |
| +		for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
 | |
| +			vreg->corner[j].open_loop_volt = cpr3_interpolate(
 | |
| +				freq_low, volt_low, freq_high, volt_high,
 | |
| +				vreg->corner[j].proc_freq);
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	if (rc == 0) {
 | |
| +		cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
 | |
| +				vreg->corner[i].open_loop_volt);
 | |
| +
 | |
| +		rc = cpr3_adjust_open_loop_voltages(vreg);
 | |
| +		if (rc)
 | |
| +			cpr3_err(vreg, "open-loop voltage adjustment failed, rc=%d\n",
 | |
| +				rc);
 | |
| +
 | |
| +		if (of_find_property(node,
 | |
| +			"qcom,cpr-misc-fuse-voltage-adjustment",
 | |
| +			NULL)) {
 | |
| +			misc_adj_volt = kcalloc(vreg->corner_count,
 | |
| +					sizeof(*misc_adj_volt), GFP_KERNEL);
 | |
| +			if (!misc_adj_volt) {
 | |
| +				rc = -ENOMEM;
 | |
| +				goto _exit;
 | |
| +			}
 | |
| +
 | |
| +			rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
 | |
| +				misc_adj_volt);
 | |
| +			if (rc) {
 | |
| +				cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
 | |
| +					rc);
 | |
| +				kfree(misc_adj_volt);
 | |
| +				goto _exit;
 | |
| +			}
 | |
| +
 | |
| +			for (i = 0; i < vreg->corner_count; i++)
 | |
| +				vreg->corner[i].open_loop_volt
 | |
| +						+= misc_adj_volt[i];
 | |
| +			kfree(misc_adj_volt);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +_exit:
 | |
| +	kfree(fuse_volt);
 | |
| +	kfree(fmax_corner);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_ipq807x_apss_set_no_interpolation_quotients() - use the fused target
 | |
| + *		quotient values for lower frequencies.
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + * @volt_adjust:	Pointer to array of per-corner closed-loop adjustment
 | |
| + *			voltages
 | |
| + * @volt_adjust_fuse:	Pointer to array of per-fuse-corner closed-loop
 | |
| + *			adjustment voltages
 | |
| + * @ro_scale:		Pointer to array of per-fuse-corner RO scaling factor
 | |
| + *			values with units of QUOT/V
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_ipq807x_apss_set_no_interpolation_quotients(
 | |
| +			struct cpr3_regulator *vreg, int *volt_adjust,
 | |
| +			int *volt_adjust_fuse, int *ro_scale)
 | |
| +{
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	u32 quot, ro;
 | |
| +	int quot_adjust;
 | |
| +	int i, fuse_corner;
 | |
| +
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		fuse_corner = vreg->corner[i].cpr_fuse_corner;
 | |
| +		quot = fuse->target_quot[fuse_corner];
 | |
| +		quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
 | |
| +					   volt_adjust_fuse[fuse_corner] +
 | |
| +					   volt_adjust[i]);
 | |
| +		ro = fuse->ro_sel[fuse_corner];
 | |
| +		vreg->corner[i].target_quot[ro] = quot + quot_adjust;
 | |
| +		cpr3_debug(vreg, "corner=%d RO=%u target quot=%u\n",
 | |
| +			  i, ro, quot);
 | |
| +
 | |
| +		if (quot_adjust)
 | |
| +			cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %u --> %u (%d uV)\n",
 | |
| +				  i, ro, quot, vreg->corner[i].target_quot[ro],
 | |
| +				  volt_adjust_fuse[fuse_corner] +
 | |
| +				  volt_adjust[i]);
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_ipq807x_apss_calculate_target_quotients() - calculate the CPR target
 | |
| + *		quotient for each corner of a CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * If target quotient interpolation is allowed in device tree, then this
 | |
| + * function calculates the target quotient for a given corner using linear
 | |
| + * interpolation.  This interpolation is performed using the processor
 | |
| + * frequencies of the lower and higher Fmax corners along with the fused
 | |
| + * target quotient and quotient offset of the higher Fmax corner.
 | |
| + *
 | |
| + * If target quotient interpolation is not allowed, then this function uses
 | |
| + * the Fmax fused target quotient for all of the corners associated with a
 | |
| + * given fuse corner.
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_ipq807x_apss_calculate_target_quotients(
 | |
| +			struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	int rc;
 | |
| +	bool allow_interpolation;
 | |
| +	u64 freq_low, freq_high, prev_quot;
 | |
| +	u64 *quot_low;
 | |
| +	u64 *quot_high;
 | |
| +	u32 quot, ro;
 | |
| +	int i, j, fuse_corner, quot_adjust;
 | |
| +	int *fmax_corner;
 | |
| +	int *volt_adjust, *volt_adjust_fuse, *ro_scale;
 | |
| +	int *voltage_adj_misc;
 | |
| +
 | |
| +	/* Log fused quotient values for debugging purposes. */
 | |
| +	for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
 | |
| +		i < vreg->fuse_corner_count; i++)
 | |
| +		cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
 | |
| +			cpr4_ipq807x_apss_fuse_corner_name[i],
 | |
| +			fuse->ro_sel[i], fuse->target_quot[i],
 | |
| +			fuse->ro_sel[i], fuse->quot_offset[i] *
 | |
| +			IPQ807x_APSS_QUOT_OFFSET_SCALE);
 | |
| +
 | |
| +	allow_interpolation = of_property_read_bool(vreg->of_node,
 | |
| +					"qcom,allow-quotient-interpolation");
 | |
| +
 | |
| +	volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
 | |
| +					GFP_KERNEL);
 | |
| +	volt_adjust_fuse = kcalloc(vreg->fuse_corner_count,
 | |
| +					sizeof(*volt_adjust_fuse), GFP_KERNEL);
 | |
| +	ro_scale = kcalloc(vreg->fuse_corner_count, sizeof(*ro_scale),
 | |
| +					GFP_KERNEL);
 | |
| +	fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
 | |
| +					GFP_KERNEL);
 | |
| +	quot_low = kcalloc(vreg->fuse_corner_count, sizeof(*quot_low),
 | |
| +					GFP_KERNEL);
 | |
| +	quot_high = kcalloc(vreg->fuse_corner_count, sizeof(*quot_high),
 | |
| +					GFP_KERNEL);
 | |
| +	if (!volt_adjust || !volt_adjust_fuse || !ro_scale ||
 | |
| +	    !fmax_corner || !quot_low || !quot_high) {
 | |
| +		rc = -ENOMEM;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_parse_closed_loop_voltage_adjustments(vreg, &fuse->ro_sel[0],
 | |
| +				volt_adjust, volt_adjust_fuse, ro_scale);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
 | |
| +			rc);
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +		"qcom,cpr-misc-fuse-voltage-adjustment", NULL)) {
 | |
| +		voltage_adj_misc = kcalloc(vreg->corner_count,
 | |
| +				sizeof(*voltage_adj_misc), GFP_KERNEL);
 | |
| +		if (!voltage_adj_misc) {
 | |
| +			rc = -ENOMEM;
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
 | |
| +			voltage_adj_misc);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
 | |
| +				rc);
 | |
| +			kfree(voltage_adj_misc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +
 | |
| +		for (i = 0; i < vreg->corner_count; i++)
 | |
| +			volt_adjust[i] += voltage_adj_misc[i];
 | |
| +
 | |
| +		kfree(voltage_adj_misc);
 | |
| +	}
 | |
| +
 | |
| +	if (!allow_interpolation) {
 | |
| +		/* Use fused target quotients for lower frequencies. */
 | |
| +		return cpr4_ipq807x_apss_set_no_interpolation_quotients(
 | |
| +				vreg, volt_adjust, volt_adjust_fuse, ro_scale);
 | |
| +	}
 | |
| +
 | |
| +	/* Determine highest corner mapped to each fuse corner */
 | |
| +	j = vreg->fuse_corner_count - 1;
 | |
| +	for (i = vreg->corner_count - 1; i >= 0; i--) {
 | |
| +		if (vreg->corner[i].cpr_fuse_corner == j) {
 | |
| +			fmax_corner[j] = i;
 | |
| +			j--;
 | |
| +		}
 | |
| +	}
 | |
| +	if (j >= 0) {
 | |
| +		cpr3_err(vreg, "invalid fuse corner mapping\n");
 | |
| +		rc = -EINVAL;
 | |
| +		goto done;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Interpolation is not possible for corners mapped to the lowest fuse
 | |
| +	 * corner so use the fuse corner value directly.
 | |
| +	 */
 | |
| +	i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
 | |
| +	quot_adjust = cpr3_quot_adjustment(ro_scale[i], volt_adjust_fuse[i]);
 | |
| +	quot = fuse->target_quot[i] + quot_adjust;
 | |
| +	quot_high[i] = quot_low[i] = quot;
 | |
| +	ro = fuse->ro_sel[i];
 | |
| +	if (quot_adjust)
 | |
| +		cpr3_debug(vreg, "adjusted fuse corner %d RO%u target quot: %llu --> %u (%d uV)\n",
 | |
| +			i, ro, fuse->target_quot[i], quot, volt_adjust_fuse[i]);
 | |
| +
 | |
| +	for (i = 0; i <= fmax_corner[CPR4_IPQ807x_APSS_FUSE_CORNER_SVS];
 | |
| +		i++)
 | |
| +		vreg->corner[i].target_quot[ro] = quot;
 | |
| +
 | |
| +	for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_NOM;
 | |
| +	     i < vreg->fuse_corner_count; i++) {
 | |
| +		quot_high[i] = fuse->target_quot[i];
 | |
| +		if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
 | |
| +			quot_low[i] = quot_high[i - 1];
 | |
| +		else
 | |
| +			quot_low[i] = quot_high[i]
 | |
| +					- fuse->quot_offset[i]
 | |
| +					  * IPQ807x_APSS_QUOT_OFFSET_SCALE;
 | |
| +		if (quot_high[i] < quot_low[i]) {
 | |
| +			cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu; overriding: quot_high[%d]=%llu\n",
 | |
| +				i, quot_high[i], i, quot_low[i],
 | |
| +				i, quot_low[i]);
 | |
| +			quot_high[i] = quot_low[i];
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Perform per-fuse-corner target quotient adjustment */
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		quot_adjust = cpr3_quot_adjustment(ro_scale[i],
 | |
| +						   volt_adjust_fuse[i]);
 | |
| +		if (quot_adjust) {
 | |
| +			prev_quot = quot_high[i];
 | |
| +			quot_high[i] += quot_adjust;
 | |
| +			cpr3_debug(vreg, "adjusted fuse corner %d RO%llu target quot: %llu --> %llu (%d uV)\n",
 | |
| +				i, fuse->ro_sel[i], prev_quot, quot_high[i],
 | |
| +				volt_adjust_fuse[i]);
 | |
| +		}
 | |
| +
 | |
| +		if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
 | |
| +			quot_low[i] = quot_high[i - 1];
 | |
| +		else
 | |
| +			quot_low[i] += cpr3_quot_adjustment(ro_scale[i],
 | |
| +						    volt_adjust_fuse[i - 1]);
 | |
| +
 | |
| +		if (quot_high[i] < quot_low[i]) {
 | |
| +			cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu after adjustment; overriding: quot_high[%d]=%llu\n",
 | |
| +				i, quot_high[i], i, quot_low[i],
 | |
| +				i, quot_low[i]);
 | |
| +			quot_high[i] = quot_low[i];
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Interpolate voltages for the higher fuse corners. */
 | |
| +	for (i = 1; i < vreg->fuse_corner_count; i++) {
 | |
| +		freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
 | |
| +		freq_high = vreg->corner[fmax_corner[i]].proc_freq;
 | |
| +
 | |
| +		ro = fuse->ro_sel[i];
 | |
| +		for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
 | |
| +			vreg->corner[j].target_quot[ro] = cpr3_interpolate(
 | |
| +				freq_low, quot_low[i], freq_high, quot_high[i],
 | |
| +				vreg->corner[j].proc_freq);
 | |
| +	}
 | |
| +
 | |
| +	/* Perform per-corner target quotient adjustment */
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		fuse_corner = vreg->corner[i].cpr_fuse_corner;
 | |
| +		ro = fuse->ro_sel[fuse_corner];
 | |
| +		quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
 | |
| +						   volt_adjust[i]);
 | |
| +		if (quot_adjust) {
 | |
| +			prev_quot = vreg->corner[i].target_quot[ro];
 | |
| +			vreg->corner[i].target_quot[ro] += quot_adjust;
 | |
| +			cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %llu --> %u (%d uV)\n",
 | |
| +				i, ro, prev_quot,
 | |
| +				vreg->corner[i].target_quot[ro],
 | |
| +				volt_adjust[i]);
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* Ensure that target quotients increase monotonically */
 | |
| +	for (i = 1; i < vreg->corner_count; i++) {
 | |
| +		ro = fuse->ro_sel[vreg->corner[i].cpr_fuse_corner];
 | |
| +		if (fuse->ro_sel[vreg->corner[i - 1].cpr_fuse_corner] == ro
 | |
| +		    && vreg->corner[i].target_quot[ro]
 | |
| +				< vreg->corner[i - 1].target_quot[ro]) {
 | |
| +			cpr3_debug(vreg, "adjusted corner %d RO%u target quot=%u < adjusted corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
 | |
| +				i, ro, vreg->corner[i].target_quot[ro],
 | |
| +				i - 1, ro, vreg->corner[i - 1].target_quot[ro],
 | |
| +				i, ro, vreg->corner[i - 1].target_quot[ro]);
 | |
| +			vreg->corner[i].target_quot[ro]
 | |
| +				= vreg->corner[i - 1].target_quot[ro];
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +done:
 | |
| +	kfree(volt_adjust);
 | |
| +	kfree(volt_adjust_fuse);
 | |
| +	kfree(ro_scale);
 | |
| +	kfree(fmax_corner);
 | |
| +	kfree(quot_low);
 | |
| +	kfree(quot_high);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_print_settings() - print out APSS CPR configuration settings into
 | |
| + *		the kernel log for debugging purposes
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + */
 | |
| +static void cpr4_apss_print_settings(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_corner *corner;
 | |
| +	int i;
 | |
| +
 | |
| +	cpr3_debug(vreg, "Corner: Frequency (Hz), Fuse Corner, Floor (uV), Open-Loop (uV), Ceiling (uV)\n");
 | |
| +	for (i = 0; i < vreg->corner_count; i++) {
 | |
| +		corner = &vreg->corner[i];
 | |
| +		cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
 | |
| +			i, corner->proc_freq, corner->cpr_fuse_corner,
 | |
| +			corner->floor_volt, corner->open_loop_volt,
 | |
| +			corner->ceiling_volt);
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->thread->ctrl->apm)
 | |
| +		cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
 | |
| +			vreg->thread->ctrl->apm_threshold_volt,
 | |
| +			vreg->thread->ctrl->apm_adj_volt);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_init_thread() - perform steps necessary to initialize the
 | |
| + *		configuration data for a CPR3 thread
 | |
| + * @thread:		Pointer to the CPR3 thread
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_init_thread(struct cpr3_thread *thread)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_common_thread_data(thread);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(thread->ctrl, "thread %u unable to read CPR thread data from device tree, rc=%d\n",
 | |
| +			thread->thread_id, rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_parse_temp_adj_properties() - parse temperature based
 | |
| + *		adjustment properties from device tree.
 | |
| + * @ctrl:	Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	struct device_node *of_node = ctrl->dev->of_node;
 | |
| +	int rc, i, len, temp_point_count;
 | |
| +
 | |
| +	if (!of_find_property(of_node, "qcom,cpr-temp-point-map", &len)) {
 | |
| +		/*
 | |
| +		 * Temperature based adjustments are not defined. Single
 | |
| +		 * temperature band is still valid for per-online-core
 | |
| +		 * adjustments.
 | |
| +		 */
 | |
| +		ctrl->temp_band_count = 1;
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	temp_point_count = len / sizeof(u32);
 | |
| +	if (temp_point_count <= 0 ||
 | |
| +	    temp_point_count > IPQ807x_APSS_MAX_TEMP_POINTS) {
 | |
| +		cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
 | |
| +			 temp_point_count, IPQ807x_APSS_MAX_TEMP_POINTS);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
 | |
| +					sizeof(*ctrl->temp_points), GFP_KERNEL);
 | |
| +	if (!ctrl->temp_points)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	rc = of_property_read_u32_array(of_node, "qcom,cpr-temp-point-map",
 | |
| +					ctrl->temp_points, temp_point_count);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading property qcom,cpr-temp-point-map, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < temp_point_count; i++)
 | |
| +		cpr3_debug(ctrl, "Temperature Point %d=%d\n", i,
 | |
| +				   ctrl->temp_points[i]);
 | |
| +
 | |
| +	/*
 | |
| +	 * If t1, t2, and t3 are the temperature points, then the temperature
 | |
| +	 * bands are: (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
 | |
| +	 */
 | |
| +	ctrl->temp_band_count = temp_point_count + 1;
 | |
| +	cpr3_debug(ctrl, "Number of temp bands =%d\n", ctrl->temp_band_count);
 | |
| +
 | |
| +	rc = of_property_read_u32(of_node, "qcom,cpr-initial-temp-band",
 | |
| +				  &ctrl->initial_temp_band);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading qcom,cpr-initial-temp-band, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->initial_temp_band >= ctrl->temp_band_count) {
 | |
| +		cpr3_err(ctrl, "Initial temperature band value %d should be in range [0 - %d]\n",
 | |
| +			ctrl->initial_temp_band, ctrl->temp_band_count - 1);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->temp_sensor_id_start = IPQ807x_APSS_TEMP_SENSOR_ID_START;
 | |
| +	ctrl->temp_sensor_id_end = IPQ807x_APSS_TEMP_SENSOR_ID_END;
 | |
| +	ctrl->allow_temp_adj = true;
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_parse_boost_properties() - parse configuration data for boost
 | |
| + *		voltage adjustment for CPR3 regulator from device tree.
 | |
| + * @vreg:	Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_parse_boost_properties(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = vreg->thread->ctrl;
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
 | |
| +	struct cpr3_corner *corner;
 | |
| +	int i, boost_voltage, final_boost_volt, rc = 0;
 | |
| +	int *boost_table = NULL, *boost_temp_adj = NULL;
 | |
| +	int boost_voltage_adjust = 0, boost_num_cores = 0;
 | |
| +	u32 boost_allowed = 0;
 | |
| +
 | |
| +	if (!boost_fuse[fuse->boost_cfg])
 | |
| +		/* Voltage boost is disabled in fuse */
 | |
| +		return 0;
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node, "qcom,allow-boost", NULL)) {
 | |
| +		rc = cpr3_parse_array_property(vreg, "qcom,allow-boost", 1,
 | |
| +				&boost_allowed);
 | |
| +		if (rc)
 | |
| +			return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (!boost_allowed) {
 | |
| +		/* Voltage boost is not enabled for this regulator */
 | |
| +		return 0;
 | |
| +	}
 | |
| +
 | |
| +	boost_voltage = cpr3_convert_open_loop_voltage_fuse(
 | |
| +				vreg->cpr4_regulator_data->boost_fuse_ref_volt,
 | |
| +				vreg->cpr4_regulator_data->fuse_step_volt,
 | |
| +				fuse->boost_voltage,
 | |
| +				IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
 | |
| +
 | |
| +	/* Log boost voltage value for debugging purposes. */
 | |
| +	cpr3_info(vreg, "Boost open-loop=%7d uV\n", boost_voltage);
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +			"qcom,cpr-boost-voltage-fuse-adjustment", NULL)) {
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +			"qcom,cpr-boost-voltage-fuse-adjustment",
 | |
| +			1, &boost_voltage_adjust);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "qcom,cpr-boost-voltage-fuse-adjustment reading failed, rc=%d\n",
 | |
| +				rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +
 | |
| +		boost_voltage += boost_voltage_adjust;
 | |
| +		/* Log boost voltage value for debugging purposes. */
 | |
| +		cpr3_info(vreg, "Adjusted boost open-loop=%7d uV\n",
 | |
| +			boost_voltage);
 | |
| +	}
 | |
| +
 | |
| +	/* Limit boost voltage value between ceiling and floor voltage limits */
 | |
| +	boost_voltage = min(boost_voltage, vreg->cpr4_regulator_data->boost_ceiling_volt);
 | |
| +	boost_voltage = max(boost_voltage, vreg->cpr4_regulator_data->boost_floor_volt);
 | |
| +
 | |
| +	/*
 | |
| +	 * The boost feature can only be used for the highest voltage corner.
 | |
| +	 * Also, keep core-count adjustments disabled when the boost feature
 | |
| +	 * is enabled.
 | |
| +	 */
 | |
| +	corner = &vreg->corner[vreg->corner_count - 1];
 | |
| +	if (!corner->sdelta) {
 | |
| +		/*
 | |
| +		 * If core-count/temp adjustments are not defined, the cpr4
 | |
| +		 * sdelta for this corner will not be allocated. Allocate it
 | |
| +		 * here for boost configuration.
 | |
| +		 */
 | |
| +		corner->sdelta = devm_kzalloc(ctrl->dev,
 | |
| +					sizeof(*corner->sdelta), GFP_KERNEL);
 | |
| +		if (!corner->sdelta)
 | |
| +			return -ENOMEM;
 | |
| +	}
 | |
| +	corner->sdelta->temp_band_count = ctrl->temp_band_count;
 | |
| +
 | |
| +	rc = of_property_read_u32(vreg->of_node, "qcom,cpr-num-boost-cores",
 | |
| +				&boost_num_cores);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "qcom,cpr-num-boost-cores reading failed, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (boost_num_cores <= 0 ||
 | |
| +	    boost_num_cores > IPQ807x_APSS_CPR_SDELTA_CORE_COUNT) {
 | |
| +		cpr3_err(vreg, "Invalid boost number of cores = %d\n",
 | |
| +			boost_num_cores);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +	corner->sdelta->boost_num_cores = boost_num_cores;
 | |
| +
 | |
| +	boost_table = devm_kcalloc(ctrl->dev, corner->sdelta->temp_band_count,
 | |
| +					sizeof(*boost_table), GFP_KERNEL);
 | |
| +	if (!boost_table)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	if (of_find_property(vreg->of_node,
 | |
| +				"qcom,cpr-boost-temp-adjustment", NULL)) {
 | |
| +		boost_temp_adj = kcalloc(corner->sdelta->temp_band_count,
 | |
| +					sizeof(*boost_temp_adj), GFP_KERNEL);
 | |
| +		if (!boost_temp_adj)
 | |
| +			return -ENOMEM;
 | |
| +
 | |
| +		rc = cpr3_parse_array_property(vreg,
 | |
| +				"qcom,cpr-boost-temp-adjustment",
 | |
| +				corner->sdelta->temp_band_count,
 | |
| +				boost_temp_adj);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(vreg, "qcom,cpr-boost-temp-adjustment reading failed, rc=%d\n",
 | |
| +				rc);
 | |
| +			goto done;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < corner->sdelta->temp_band_count; i++) {
 | |
| +		/* Apply static adjustments to boost voltage */
 | |
| +		final_boost_volt = boost_voltage + (boost_temp_adj == NULL
 | |
| +						? 0 : boost_temp_adj[i]);
 | |
| +		/*
 | |
| +		 * Limit final adjusted boost voltage value between ceiling
 | |
| +		 * and floor voltage limits
 | |
| +		 */
 | |
| +		final_boost_volt = min(final_boost_volt,
 | |
| +					vreg->cpr4_regulator_data->boost_ceiling_volt);
 | |
| +		final_boost_volt = max(final_boost_volt,
 | |
| +					vreg->cpr4_regulator_data->boost_floor_volt);
 | |
| +
 | |
| +		boost_table[i] = (corner->open_loop_volt - final_boost_volt)
 | |
| +					/ ctrl->step_volt;
 | |
| +		cpr3_debug(vreg, "Adjusted boost voltage margin for temp band %d = %d steps\n",
 | |
| +			i, boost_table[i]);
 | |
| +	}
 | |
| +
 | |
| +	corner->ceiling_volt = vreg->cpr4_regulator_data->boost_ceiling_volt;
 | |
| +	corner->sdelta->boost_table = boost_table;
 | |
| +	corner->sdelta->allow_boost = true;
 | |
| +	corner->sdelta->allow_core_count_adj = false;
 | |
| +	vreg->allow_boost = true;
 | |
| +	ctrl->allow_boost = true;
 | |
| +done:
 | |
| +	kfree(boost_temp_adj);
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_init_regulator() - perform all steps necessary to initialize the
 | |
| + *		configuration data for a CPR3 regulator
 | |
| + * @vreg:		Pointer to the CPR3 regulator
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_init_regulator(struct cpr3_regulator *vreg)
 | |
| +{
 | |
| +	struct cpr4_ipq807x_apss_fuses *fuse;
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr4_ipq807x_apss_read_fuse_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	fuse = vreg->platform_fuses;
 | |
| +
 | |
| +	rc = cpr4_apss_parse_corner_data(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to read CPR corner data from device tree, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_mem_acc_init(vreg);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(vreg, "unable to initialize mem-acc regulator settings, rc=%d\n",
 | |
| +				 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_ipq807x_apss_calculate_open_loop_voltages(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to calculate open-loop voltages, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_limit_open_loop_voltages(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	cpr3_open_loop_voltage_as_ceiling(vreg);
 | |
| +
 | |
| +	rc = cpr3_limit_floor_voltages(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_ipq807x_apss_calculate_target_quotients(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to parse temperature and core count voltage adjustments, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (vreg->allow_core_count_adj && (vreg->max_core_count <= 0
 | |
| +				   || vreg->max_core_count >
 | |
| +				   IPQ807x_APSS_CPR_SDELTA_CORE_COUNT)) {
 | |
| +		cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
 | |
| +			 vreg->max_core_count);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_apss_parse_boost_properties(vreg);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(vreg, "unable to parse boost adjustments, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	cpr4_apss_print_settings(vreg);
 | |
| +
 | |
| +	return rc;
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + * cpr4_apss_init_controller() - perform APSS CPR4 controller specific
 | |
| + *		initializations
 | |
| + * @ctrl:		Pointer to the CPR3 controller
 | |
| + *
 | |
| + * Return: 0 on success, errno on failure
 | |
| + */
 | |
| +static int cpr4_apss_init_controller(struct cpr3_controller *ctrl)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = cpr3_parse_common_ctrl_data(ctrl);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
 | |
| +				rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = of_property_read_u32(ctrl->dev->of_node,
 | |
| +				  "qcom,cpr-down-error-step-limit",
 | |
| +				  &ctrl->down_error_step_limit);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading qcom,cpr-down-error-step-limit, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = of_property_read_u32(ctrl->dev->of_node,
 | |
| +				  "qcom,cpr-up-error-step-limit",
 | |
| +				  &ctrl->up_error_step_limit);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "error reading qcom,cpr-up-error-step-limit, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	/*
 | |
| +	 * Use fixed step quotient if specified otherwise use dynamic
 | |
| +	 * calculated per RO step quotient
 | |
| +	 */
 | |
| +	of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-step-quot-fixed",
 | |
| +			&ctrl->step_quot_fixed);
 | |
| +	ctrl->use_dynamic_step_quot = ctrl->step_quot_fixed ? false : true;
 | |
| +
 | |
| +	ctrl->saw_use_unit_mV = of_property_read_bool(ctrl->dev->of_node,
 | |
| +					"qcom,cpr-saw-use-unit-mV");
 | |
| +
 | |
| +	of_property_read_u32(ctrl->dev->of_node,
 | |
| +			"qcom,cpr-voltage-settling-time",
 | |
| +			&ctrl->voltage_settling_time);
 | |
| +
 | |
| +	if (of_find_property(ctrl->dev->of_node, "vdd-limit-supply", NULL)) {
 | |
| +		ctrl->vdd_limit_regulator =
 | |
| +			devm_regulator_get(ctrl->dev, "vdd-limit");
 | |
| +		if (IS_ERR(ctrl->vdd_limit_regulator)) {
 | |
| +			rc = PTR_ERR(ctrl->vdd_limit_regulator);
 | |
| +			if (rc != -EPROBE_DEFER)
 | |
| +				cpr3_err(ctrl, "unable to request vdd-limit regulator, rc=%d\n",
 | |
| +					 rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_apm_init(ctrl);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "unable to initialize APM settings, rc=%d\n",
 | |
| +				rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_apss_parse_temp_adj_properties(ctrl);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "unable to parse temperature adjustment properties, rc=%d\n",
 | |
| +			 rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	ctrl->sensor_count = IPQ807x_APSS_CPR_SENSOR_COUNT;
 | |
| +
 | |
| +	/*
 | |
| +	 * APSS only has one thread (0) per controller so the zeroed
 | |
| +	 * array does not need further modification.
 | |
| +	 */
 | |
| +	ctrl->sensor_owner = devm_kcalloc(ctrl->dev, ctrl->sensor_count,
 | |
| +		sizeof(*ctrl->sensor_owner), GFP_KERNEL);
 | |
| +	if (!ctrl->sensor_owner)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4;
 | |
| +	ctrl->supports_hw_closed_loop = false;
 | |
| +	ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
 | |
| +						"qcom,cpr-hw-closed-loop");
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int cpr4_apss_regulator_suspend(struct platform_device *pdev,
 | |
| +				pm_message_t state)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
 | |
| +
 | |
| +	return cpr3_regulator_suspend(ctrl);
 | |
| +}
 | |
| +
 | |
| +static int cpr4_apss_regulator_resume(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
 | |
| +
 | |
| +	return cpr3_regulator_resume(ctrl);
 | |
| +}
 | |
| +
 | |
| +static void ipq6018_set_mem_acc(struct regulator_dev *rdev)
 | |
| +{
 | |
| +	struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
 | |
| +
 | |
| +	ipq6018_mem_acc_tcsr[0].ioremap_addr =
 | |
| +		ioremap(ipq6018_mem_acc_tcsr[0].phy_addr, 0x4);
 | |
| +	ipq6018_mem_acc_tcsr[1].ioremap_addr =
 | |
| +		ioremap(ipq6018_mem_acc_tcsr[1].phy_addr, 0x4);
 | |
| +
 | |
| +	if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
 | |
| +			(ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
 | |
| +			(vreg->current_corner == (vreg->corner_count - CPR3_CORNER_OFFSET))) {
 | |
| +
 | |
| +		writel_relaxed(ipq6018_mem_acc_tcsr[0].value,
 | |
| +				ipq6018_mem_acc_tcsr[0].ioremap_addr);
 | |
| +		writel_relaxed(ipq6018_mem_acc_tcsr[1].value,
 | |
| +				ipq6018_mem_acc_tcsr[1].ioremap_addr);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +static void ipq6018_clr_mem_acc(struct regulator_dev *rdev)
 | |
| +{
 | |
| +	struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
 | |
| +
 | |
| +	if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
 | |
| +			(ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
 | |
| +			(vreg->current_corner != vreg->corner_count - CPR3_CORNER_OFFSET)) {
 | |
| +		writel_relaxed(0x0, ipq6018_mem_acc_tcsr[0].ioremap_addr);
 | |
| +		writel_relaxed(0x0, ipq6018_mem_acc_tcsr[1].ioremap_addr);
 | |
| +	}
 | |
| +
 | |
| +	iounmap(ipq6018_mem_acc_tcsr[0].ioremap_addr);
 | |
| +	iounmap(ipq6018_mem_acc_tcsr[1].ioremap_addr);
 | |
| +}
 | |
| +
 | |
| +static struct cpr4_mem_acc_func ipq6018_mem_acc_funcs = {
 | |
| +	.set_mem_acc = ipq6018_set_mem_acc,
 | |
| +	.clear_mem_acc = ipq6018_clr_mem_acc
 | |
| +};
 | |
| +
 | |
| +static const struct cpr4_reg_data ipq807x_cpr_apss = {
 | |
| +	.cpr_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS,
 | |
| +	.fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
 | |
| +	.boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
 | |
| +	.boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
 | |
| +	.boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
 | |
| +	.cpr3_fuse_params = &ipq807x_fuse_params,
 | |
| +	.mem_acc_funcs = NULL,
 | |
| +};
 | |
| +
 | |
| +static const struct cpr4_reg_data ipq817x_cpr_apss = {
 | |
| +	.cpr_valid_fuse_count = IPQ817x_APPS_FUSE_CORNERS,
 | |
| +	.fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
 | |
| +	.boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
 | |
| +	.boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
 | |
| +	.boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
 | |
| +	.cpr3_fuse_params = &ipq807x_fuse_params,
 | |
| +	.mem_acc_funcs = NULL,
 | |
| +};
 | |
| +
 | |
| +static const struct cpr4_reg_data ipq6018_cpr_apss = {
 | |
| +	.cpr_valid_fuse_count = IPQ6018_APSS_FUSE_CORNERS,
 | |
| +	.fuse_ref_volt = ipq6018_apss_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ6018_APSS_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
 | |
| +	.boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
 | |
| +	.boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
 | |
| +	.boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
 | |
| +	.cpr3_fuse_params = &ipq6018_fuse_params,
 | |
| +	.mem_acc_funcs = &ipq6018_mem_acc_funcs,
 | |
| +};
 | |
| +
 | |
| +static const struct cpr4_reg_data ipq9574_cpr_apss = {
 | |
| +	.cpr_valid_fuse_count = IPQ9574_APSS_FUSE_CORNERS,
 | |
| +	.fuse_ref_volt = ipq9574_apss_fuse_ref_volt,
 | |
| +	.fuse_step_volt = IPQ9574_APSS_FUSE_STEP_VOLT,
 | |
| +	.cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
 | |
| +	.boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
 | |
| +	.boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
 | |
| +	.boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
 | |
| +	.cpr3_fuse_params = &ipq9574_fuse_params,
 | |
| +	.mem_acc_funcs = NULL,
 | |
| +};
 | |
| +
 | |
| +static struct of_device_id cpr4_regulator_match_table[] = {
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr4-ipq807x-apss-regulator",
 | |
| +		.data = &ipq807x_cpr_apss
 | |
| +	},
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr4-ipq817x-apss-regulator",
 | |
| +		.data = &ipq817x_cpr_apss
 | |
| +	},
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr4-ipq6018-apss-regulator",
 | |
| +		.data = &ipq6018_cpr_apss
 | |
| +	},
 | |
| +	{
 | |
| +		.compatible = "qcom,cpr4-ipq9574-apss-regulator",
 | |
| +		.data = &ipq9574_cpr_apss
 | |
| +	},
 | |
| +	{}
 | |
| +};
 | |
| +
 | |
| +static int cpr4_apss_regulator_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct cpr3_controller *ctrl;
 | |
| +	const struct of_device_id *match;
 | |
| +	struct cpr4_reg_data *cpr_data;
 | |
| +	int i, rc;
 | |
| +
 | |
| +	if (!dev->of_node) {
 | |
| +		dev_err(dev, "Device tree node is missing\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
 | |
| +	if (!ctrl)
 | |
| +		return -ENOMEM;
 | |
| +
 | |
| +	match = of_match_device(cpr4_regulator_match_table, &pdev->dev);
 | |
| +	if (!match)
 | |
| +		return -ENODEV;
 | |
| +
 | |
| +	cpr_data = (struct cpr4_reg_data *)match->data;
 | |
| +	g_valid_fuse_count = cpr_data->cpr_valid_fuse_count;
 | |
| +	dev_info(dev, "CPR valid fuse count: %d\n", g_valid_fuse_count);
 | |
| +	ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
 | |
| +
 | |
| +	ctrl->dev = dev;
 | |
| +	/* Set to false later if anything precludes CPR operation. */
 | |
| +	ctrl->cpr_allowed_hw = true;
 | |
| +
 | |
| +	rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
 | |
| +					&ctrl->name);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_map_fuse_base(ctrl, pdev);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "could not map fuse base address\n");
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_APSS_CPR_TCSR_START,
 | |
| +				    IPQ807x_APSS_CPR_TCSR_END);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "could not read CPR tcsr setting\n");
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr3_allocate_threads(ctrl, 0, 0);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
 | |
| +			rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	if (ctrl->thread_count != 1) {
 | |
| +		cpr3_err(ctrl, "expected 1 thread but found %d\n",
 | |
| +			ctrl->thread_count);
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_apss_init_controller(ctrl);
 | |
| +	if (rc) {
 | |
| +		if (rc != -EPROBE_DEFER)
 | |
| +			cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
 | |
| +				rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	rc = cpr4_apss_init_thread(&ctrl->thread[0]);
 | |
| +	if (rc) {
 | |
| +		cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
 | |
| +		return rc;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
 | |
| +		ctrl->thread[0].vreg[i].cpr4_regulator_data = cpr_data;
 | |
| +		rc = cpr4_apss_init_regulator(&ctrl->thread[0].vreg[i]);
 | |
| +		if (rc) {
 | |
| +			cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
 | |
| +				 rc);
 | |
| +			return rc;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	platform_set_drvdata(pdev, ctrl);
 | |
| +
 | |
| +	return cpr3_regulator_register(pdev, ctrl);
 | |
| +}
 | |
| +
 | |
| +static int cpr4_apss_regulator_remove(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
 | |
| +
 | |
| +	return cpr3_regulator_unregister(ctrl);
 | |
| +}
 | |
| +
 | |
| +static struct platform_driver cpr4_apss_regulator_driver = {
 | |
| +	.driver		= {
 | |
| +		.name		= "qcom,cpr4-apss-regulator",
 | |
| +		.of_match_table	= cpr4_regulator_match_table,
 | |
| +		.owner		= THIS_MODULE,
 | |
| +	},
 | |
| +	.probe		= cpr4_apss_regulator_probe,
 | |
| +	.remove		= cpr4_apss_regulator_remove,
 | |
| +	.suspend	= cpr4_apss_regulator_suspend,
 | |
| +	.resume		= cpr4_apss_regulator_resume,
 | |
| +};
 | |
| +
 | |
| +static int cpr4_regulator_init(void)
 | |
| +{
 | |
| +	return platform_driver_register(&cpr4_apss_regulator_driver);
 | |
| +}
 | |
| +
 | |
| +static void cpr4_regulator_exit(void)
 | |
| +{
 | |
| +	platform_driver_unregister(&cpr4_apss_regulator_driver);
 | |
| +}
 | |
| +
 | |
| +MODULE_DESCRIPTION("CPR4 APSS regulator driver");
 | |
| +MODULE_LICENSE("GPL v2");
 | |
| +
 | |
| +arch_initcall(cpr4_regulator_init);
 | |
| +module_exit(cpr4_regulator_exit);
 | |
| --- /dev/null
 | |
| +++ b/include/soc/qcom/socinfo.h
 | |
| @@ -0,0 +1,463 @@
 | |
| +/* Copyright (c) 2009-2014, 2016, 2020, The Linux Foundation. All rights reserved.
 | |
| + *
 | |
| + * This program is free software; you can redistribute it and/or modify
 | |
| + * it under the terms of the GNU General Public License version 2 and
 | |
| + * only version 2 as published by the Free Software Foundation.
 | |
| + *
 | |
| + * This program is distributed in the hope that it will be useful,
 | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| + * GNU General Public License for more details.
 | |
| + *
 | |
| + */
 | |
| +
 | |
| +#ifndef _ARCH_ARM_MACH_MSM_SOCINFO_H_
 | |
| +#define _ARCH_ARM_MACH_MSM_SOCINFO_H_
 | |
| +
 | |
| +#include <linux/of.h>
 | |
| +
 | |
| +#define CPU_IPQ8074 323
 | |
| +#define CPU_IPQ8072 342
 | |
| +#define CPU_IPQ8076 343
 | |
| +#define CPU_IPQ8078 344
 | |
| +#define CPU_IPQ8070 375
 | |
| +#define CPU_IPQ8071 376
 | |
| +
 | |
| +#define CPU_IPQ8072A 389
 | |
| +#define CPU_IPQ8074A 390
 | |
| +#define CPU_IPQ8076A 391
 | |
| +#define CPU_IPQ8078A 392
 | |
| +#define CPU_IPQ8070A 395
 | |
| +#define CPU_IPQ8071A 396
 | |
| +
 | |
| +#define CPU_IPQ8172  397
 | |
| +#define CPU_IPQ8173  398
 | |
| +#define CPU_IPQ8174  399
 | |
| +
 | |
| +#define CPU_IPQ6018 402
 | |
| +#define CPU_IPQ6028 403
 | |
| +#define CPU_IPQ6000 421
 | |
| +#define CPU_IPQ6010 422
 | |
| +#define CPU_IPQ6005 453
 | |
| +
 | |
| +#define CPU_IPQ5010 446
 | |
| +#define CPU_IPQ5018 447
 | |
| +#define CPU_IPQ5028 448
 | |
| +#define CPU_IPQ5000 503
 | |
| +#define CPU_IPQ0509 504
 | |
| +#define CPU_IPQ0518 505
 | |
| +
 | |
| +#define CPU_IPQ9514 510
 | |
| +#define CPU_IPQ9554 512
 | |
| +#define CPU_IPQ9570 513
 | |
| +#define CPU_IPQ9574 514
 | |
| +#define CPU_IPQ9550 511
 | |
| +#define CPU_IPQ9510 521
 | |
| +
 | |
| +static inline int read_ipq_soc_version_major(void)
 | |
| +{
 | |
| +	const int *prop;
 | |
| +	prop = of_get_property(of_find_node_by_path("/"), "soc_version_major",
 | |
| +				NULL);
 | |
| +
 | |
| +	if (!prop)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	return le32_to_cpu(*prop);
 | |
| +}
 | |
| +
 | |
| +static inline int read_ipq_cpu_type(void)
 | |
| +{
 | |
| +	const int *prop;
 | |
| +	prop = of_get_property(of_find_node_by_path("/"), "cpu_type", NULL);
 | |
| +	/*
 | |
| +	 * Return Default CPU type if "cpu_type" property is not found in DTSI
 | |
| +	 */
 | |
| +	if (!prop)
 | |
| +		return CPU_IPQ8074;
 | |
| +
 | |
| +	return le32_to_cpu(*prop);
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8070(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8070;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8071(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8071;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8072(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8072;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8074(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8074;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8076(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8076;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8078(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8078;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8072a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8072A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8074a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8074A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8076a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8076A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8078a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8078A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8070a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8070A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8071a(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8071A;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8172(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8172;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8173(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8173;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq8174(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ8174;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq6018(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ6018;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq6028(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ6028;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq6000(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ6000;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq6010(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ6010;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq6005(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ6005;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq5010(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ5010;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq5018(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ5018;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq5028(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ5028;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq5000(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ5000;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq0509(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ0509;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq0518(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ0518;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9514(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9514;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9554(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9554;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9570(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9570;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9574(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9574;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9550(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9550;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq9510(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return read_ipq_cpu_type() == CPU_IPQ9510;
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq807x(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq8072() || cpu_is_ipq8074() ||
 | |
| +		cpu_is_ipq8076() || cpu_is_ipq8078() ||
 | |
| +		cpu_is_ipq8070() || cpu_is_ipq8071() ||
 | |
| +		cpu_is_ipq8072a() || cpu_is_ipq8074a() ||
 | |
| +		cpu_is_ipq8076a() || cpu_is_ipq8078a() ||
 | |
| +		cpu_is_ipq8070a() || cpu_is_ipq8071a() ||
 | |
| +		cpu_is_ipq8172() || cpu_is_ipq8173() ||
 | |
| +		cpu_is_ipq8174();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq60xx(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq6018() || cpu_is_ipq6028() ||
 | |
| +		cpu_is_ipq6000() || cpu_is_ipq6010() ||
 | |
| +		cpu_is_ipq6005();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq50xx(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq5010() || cpu_is_ipq5018() ||
 | |
| +		cpu_is_ipq5028() || cpu_is_ipq5000() ||
 | |
| +		cpu_is_ipq0509() || cpu_is_ipq0518();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_ipq95xx(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq9514() || cpu_is_ipq9554() ||
 | |
| +		cpu_is_ipq9570() || cpu_is_ipq9574() ||
 | |
| +		cpu_is_ipq9550() || cpu_is_ipq9510();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_nss_crypto_enabled(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq807x() || cpu_is_ipq60xx() ||
 | |
| +		cpu_is_ipq50xx() || cpu_is_ipq9570() ||
 | |
| +		cpu_is_ipq9550() || cpu_is_ipq9574() ||
 | |
| +		cpu_is_ipq9554();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_internal_wifi_enabled(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq807x() || cpu_is_ipq60xx() ||
 | |
| +		cpu_is_ipq50xx() || cpu_is_ipq9514() ||
 | |
| +		cpu_is_ipq9554() || cpu_is_ipq9574();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_uniphy1_enabled(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq807x() || cpu_is_ipq60xx() ||
 | |
| +		cpu_is_ipq9554() || cpu_is_ipq9570() ||
 | |
| +		cpu_is_ipq9574() || cpu_is_ipq9550();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +static inline int cpu_is_uniphy2_enabled(void)
 | |
| +{
 | |
| +#ifdef CONFIG_ARCH_QCOM
 | |
| +	return  cpu_is_ipq807x() || cpu_is_ipq9570() ||
 | |
| +		cpu_is_ipq9574();
 | |
| +#else
 | |
| +	return 0;
 | |
| +#endif
 | |
| +}
 | |
| +
 | |
| +#endif /* _ARCH_ARM_MACH_MSM_SOCINFO_H_ */
 |