diff options
Diffstat (limited to 'drivers/regulator/cpr3-util.c')
-rw-r--r-- | drivers/regulator/cpr3-util.c | 2149 |
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); + } +} |