summaryrefslogtreecommitdiff
path: root/drivers/regulator/cpr3-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator/cpr3-util.c')
-rw-r--r--drivers/regulator/cpr3-util.c2149
1 files changed, 2149 insertions, 0 deletions
diff --git a/drivers/regulator/cpr3-util.c b/drivers/regulator/cpr3-util.c
new file mode 100644
index 000000000000..60fe825ca013
--- /dev/null
+++ b/drivers/regulator/cpr3-util.c
@@ -0,0 +1,2149 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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 "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_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);
+ }
+
+ /*
+ * For CPRh compliant controllers two additional corners are
+ * allocated to correspond to the APM crossover voltage and the MEM ACC
+ * crossover voltage.
+ */
+ vreg->corner = devm_kcalloc(ctrl->dev, ctrl->ctrl_type ==
+ CPR_CTRL_TYPE_CPRH ?
+ vreg->corner_count + 2 :
+ 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");
+
+ 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;
+
+ 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) {
+ /* vdd-supply is optional for CPRh controllers. */
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ cpr3_debug(ctrl, "unable to request vdd regulator, rc=%d\n",
+ rc);
+ ctrl->vdd_regulator = NULL;
+ return 0;
+ }
+ cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
+ rc);
+ }
+ return rc;
+ }
+
+ /*
+ * Regulator device handles are not necessary for CPRh controllers
+ * since communication with the regulators is completely managed
+ * in hardware.
+ */
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ return rc;
+
+ 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_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_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;
+
+ if (!of_find_property(vreg->of_node,
+ "qcom,cpr-open-loop-voltage-fuse-adjustment", 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,
+ "qcom,cpr-open-loop-voltage-fuse-adjustment",
+ 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;
+
+ if (!of_find_property(vreg->of_node,
+ "qcom,cpr-closed-loop-voltage-adjustment", NULL)
+ && !of_find_property(vreg->of_node,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment", NULL)
+ && !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;
+ }
+
+ 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, "qcom,cpr-ro-scaling-factor",
+ 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,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment", NULL)) {
+ rc = cpr3_parse_array_property(vreg,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment",
+ 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,
+ "qcom,cpr-closed-loop-voltage-adjustment", NULL)) {
+ 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;
+ }
+ }
+
+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;
+ snprintf(str, sizeof(str), use_corner_band ?
+ "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n"
+ : "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;
+
+ snprintf(str, sizeof(str), use_corner_band ?
+ "qcom,cpr-corner-band%d-temp-core-voltage-adjustment" :
+ "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->max_core_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), 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), 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);
+ }
+}