| /* |
| * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| /* |
| * This file contains utility functions to be used by platform specific CPR3 |
| * regulator drivers. |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/cpumask.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| |
| #include "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; |
| |
| ctrl->ignore_invalid_fuses = of_property_read_bool(ctrl->dev->of_node, |
| "qcom,cpr-ignore-invalid-fuses"); |
| |
| /* Aging reference voltage is optional */ |
| ctrl->aging_ref_volt = 0; |
| of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-aging-ref-voltage", |
| &ctrl->aging_ref_volt); |
| |
| /* Aging possible bitmask is optional */ |
| ctrl->aging_possible_mask = 0; |
| of_property_read_u32(ctrl->dev->of_node, |
| "qcom,cpr-aging-allowed-reg-mask", |
| &ctrl->aging_possible_mask); |
| |
| if (ctrl->aging_possible_mask) { |
| /* |
| * Aging possible register value required if bitmask is |
| * specified |
| */ |
| rc = cpr3_parse_ctrl_u32(ctrl, |
| "qcom,cpr-aging-allowed-reg-value", |
| &ctrl->aging_possible_val, 0, UINT_MAX); |
| if (rc) |
| return rc; |
| } |
| |
| if (of_find_property(ctrl->dev->of_node, "clock-names", NULL)) { |
| ctrl->core_clk = devm_clk_get(ctrl->dev, "core_clk"); |
| if (IS_ERR(ctrl->core_clk)) { |
| rc = PTR_ERR(ctrl->core_clk); |
| if (rc != -EPROBE_DEFER) |
| cpr3_err(ctrl, "unable request core clock, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| rc = cpr3_panic_notifier_init(ctrl); |
| if (rc) |
| return rc; |
| |
| if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) { |
| ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd"); |
| if (IS_ERR(ctrl->vdd_regulator)) { |
| rc = PTR_ERR(ctrl->vdd_regulator); |
| if (rc != -EPROBE_DEFER) |
| cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) { |
| /* vdd-supply is optional for CPRh controllers. */ |
| ctrl->vdd_regulator = NULL; |
| } else { |
| cpr3_err(ctrl, "vdd supply is not defined\n"); |
| return -ENODEV; |
| } |
| |
| /* |
| * Reset step_quot to default on each loop_en = 0 transition is |
| * optional. |
| */ |
| ctrl->reset_step_quot_loop_en |
| = of_property_read_bool(ctrl->dev->of_node, |
| "qcom,cpr-reset-step-quot-loop-en"); |
| |
| /* |
| * 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->temp_band_count + j]); |
| cpr3_debug(vreg, "sdelta[%d]:%s\n", i, buf); |
| } |
| |
| kfree(buf); |
| return rc; |
| } |
| |
| /** |
| * cpr4_parse_core_count_temp_voltage_adj() - parse configuration data for |
| * per-online-core and per-temperature voltage adjustment for |
| * a CPR3 regulator from device tree. |
| * @vreg: Pointer to the CPR3 regulator |
| * @use_corner_band: Boolean indicating if the CPR3 regulator supports |
| * adjustments per corner band |
| * |
| * This function supports parsing of per-online-core and per-temperature |
| * adjustments per corner or per corner band. CPR controllers which support |
| * corner bands apply the same adjustments to all corners within a corner band. |
| * |
| * Return: 0 on success, errno on failure |
| */ |
| int cpr4_parse_core_count_temp_voltage_adj( |
| struct cpr3_regulator *vreg, bool use_corner_band) |
| { |
| struct cpr3_controller *ctrl = vreg->thread->ctrl; |
| struct device_node *node = vreg->of_node; |
| struct cpr3_corner *corner; |
| struct cpr4_sdelta *sdelta; |
| int i, sdelta_table_count, rc = 0; |
| int *allow_core_count_adj = NULL, *allow_temp_adj = NULL; |
| char prop_str[75]; |
| |
| if (of_find_property(node, use_corner_band ? |
| "qcom,corner-band-allow-temp-adjustment" |
| : "qcom,corner-allow-temp-adjustment", NULL)) { |
| if (!ctrl->allow_temp_adj) { |
| cpr3_err(ctrl, "Temperature adjustment configurations missing\n"); |
| return -EINVAL; |
| } |
| |
| vreg->allow_temp_adj = true; |
| } |
| |
| if (of_find_property(node, use_corner_band ? |
| "qcom,corner-band-allow-core-count-adjustment" |
| : "qcom,corner-allow-core-count-adjustment", |
| NULL)) { |
| rc = of_property_read_u32(node, "qcom,max-core-count", |
| &vreg->max_core_count); |
| if (rc) { |
| cpr3_err(vreg, "error reading qcom,max-core-count, rc=%d\n", |
| rc); |
| return -EINVAL; |
| } |
| |
| vreg->allow_core_count_adj = true; |
| ctrl->allow_core_count_adj = true; |
| } |
| |
| if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) { |
| /* |
| * Both per-online-core and temperature based adjustments are |
| * disabled for this regulator. |
| */ |
| return 0; |
| } else if (!vreg->allow_core_count_adj) { |
| /* |
| * Only per-temperature voltage adjusments are allowed. |
| * Keep max core count value as 1 to allocate SDELTA. |
| */ |
| vreg->max_core_count = 1; |
| } |
| |
| if (vreg->allow_core_count_adj) { |
| allow_core_count_adj = kcalloc(vreg->corner_count, |
| sizeof(*allow_core_count_adj), |
| GFP_KERNEL); |
| if (!allow_core_count_adj) |
| return -ENOMEM; |
| |
| snprintf(prop_str, sizeof(prop_str), 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); |
| } |
| } |
| |
| /** |
| * cpr3_apply_closed_loop_offset_voltages() - modify the closed-loop voltage |
| * adjustments by the amounts that are needed for this |
| * fuse combo |
| * @vreg: Pointer to the CPR3 regulator |
| * @volt_adjust: Array of closed-loop voltage adjustment values of length |
| * vreg->corner_count which is further adjusted based upon |
| * offset voltage fuse values. |
| * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length |
| * vreg->fuse_corner_count. |
| * |
| * Return: 0 on success, errno on failure |
| */ |
| static int cpr3_apply_closed_loop_offset_voltages(struct cpr3_regulator *vreg, |
| int *volt_adjust, int *fuse_volt_adjust) |
| { |
| u32 *corner_map; |
| int rc = 0, i; |
| |
| if (!of_find_property(vreg->of_node, |
| "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL)) { |
| /* No closed-loop offset required. */ |
| return 0; |
| } |
| |
| corner_map = kcalloc(vreg->corner_count, sizeof(*corner_map), |
| GFP_KERNEL); |
| if (!corner_map) |
| return -ENOMEM; |
| |
| rc = cpr3_parse_corner_array_property(vreg, |
| "qcom,cpr-fused-closed-loop-voltage-adjustment-map", |
| 1, corner_map); |
| if (rc) |
| goto done; |
| |
| for (i = 0; i < vreg->corner_count; i++) { |
| if (corner_map[i] == 0) { |
| continue; |
| } else if (corner_map[i] > vreg->fuse_corner_count) { |
| cpr3_err(vreg, "corner %d mapped to invalid fuse corner: %u\n", |
| i, corner_map[i]); |
| rc = -EINVAL; |
| goto done; |
| } |
| |
| volt_adjust[i] += fuse_volt_adjust[corner_map[i] - 1]; |
| } |
| |
| done: |
| kfree(corner_map); |
| return rc; |
| } |
| |
| /** |
| * cpr3_enforce_inc_quotient_monotonicity() - Ensure that target quotients |
| * increase monotonically from lower to higher corners |
| * @vreg: Pointer to the CPR3 regulator |
| * |
| * Return: 0 on success, errno on failure |
| */ |
| static void cpr3_enforce_inc_quotient_monotonicity(struct cpr3_regulator *vreg) |
| { |
| int i, j; |
| |
| for (i = 1; i < vreg->corner_count; i++) { |
| for (j = 0; j < CPR3_RO_COUNT; j++) { |
| if (vreg->corner[i].target_quot[j] |
| && vreg->corner[i].target_quot[j] |
| < vreg->corner[i - 1].target_quot[j]) { |
| cpr3_debug(vreg, "corner %d RO%u target quot=%u < corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n", |
| i, j, |
| vreg->corner[i].target_quot[j], |
| i - 1, j, |
| vreg->corner[i - 1].target_quot[j], |
| i, j, |
| vreg->corner[i - 1].target_quot[j]); |
| vreg->corner[i].target_quot[j] |
| = vreg->corner[i - 1].target_quot[j]; |
| } |
| } |
| } |
| } |
| |
| /** |
| * cpr3_enforce_dec_quotient_monotonicity() - Ensure that target quotients |
| * decrease monotonically from higher to lower corners |
| * @vreg: Pointer to the CPR3 regulator |
| * |
| * Return: 0 on success, errno on failure |
| */ |
| static void cpr3_enforce_dec_quotient_monotonicity(struct cpr3_regulator *vreg) |
| { |
| int i, j; |
| |
| for (i = vreg->corner_count - 2; i >= 0; i--) { |
| for (j = 0; j < CPR3_RO_COUNT; j++) { |
| if (vreg->corner[i + 1].target_quot[j] |
| && vreg->corner[i].target_quot[j] |
| > vreg->corner[i + 1].target_quot[j]) { |
| cpr3_debug(vreg, "corner %d RO%u target quot=%u > corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n", |
| i, j, |
| vreg->corner[i].target_quot[j], |
| i + 1, j, |
| vreg->corner[i + 1].target_quot[j], |
| i, j, |
| vreg->corner[i + 1].target_quot[j]); |
| vreg->corner[i].target_quot[j] |
| = vreg->corner[i + 1].target_quot[j]; |
| } |
| } |
| } |
| } |
| |
| /** |
| * _cpr3_adjust_target_quotients() - adjust the target quotients for each |
| * corner of the regulator according to input adjustment and |
| * scaling arrays |
| * @vreg: Pointer to the CPR3 regulator |
| * @volt_adjust: Pointer to an array of closed-loop voltage adjustments |
| * with units of microvolts. The array must have |
| * vreg->corner_count number of elements. |
| * @ro_scale: Pointer to a flattened 2D array of RO scaling factors. |
| * The array must have an inner dimension of CPR3_RO_COUNT |
| * and an outer dimension of vreg->corner_count |
| * @label: Null terminated string providing a label for the type |
| * of adjustment. |
| * |
| * Return: true if any corners received a positive voltage adjustment (> 0), |
| * else false |
| */ |
| static bool _cpr3_adjust_target_quotients(struct cpr3_regulator *vreg, |
| const int *volt_adjust, const int *ro_scale, const char *label) |
| { |
| int i, j, quot_adjust; |
| bool is_increasing = false; |
| u32 prev_quot; |
| |
| for (i = 0; i < vreg->corner_count; i++) { |
| for (j = 0; j < CPR3_RO_COUNT; j++) { |
| if (vreg->corner[i].target_quot[j]) { |
| quot_adjust = cpr3_quot_adjustment( |
| ro_scale[i * CPR3_RO_COUNT + j], |
| volt_adjust[i]); |
| if (quot_adjust) { |
| prev_quot = vreg->corner[i]. |
| target_quot[j]; |
| vreg->corner[i].target_quot[j] |
| += quot_adjust; |
| cpr3_debug(vreg, "adjusted corner %d RO%d target quot %s: %u --> %u (%d uV)\n", |
| i, j, label, prev_quot, |
| vreg->corner[i].target_quot[j], |
| volt_adjust[i]); |
| } |
| } |
| } |
| if (volt_adjust[i] > 0) |
| is_increasing = true; |
| } |
| |
| return is_increasing; |
| } |
| |
| /** |
| * cpr3_adjust_target_quotients() - adjust the target quotients for each |
| * corner according to device tree values and fuse values |
| * @vreg: Pointer to the CPR3 regulator |
| * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length |
| * vreg->fuse_corner_count. This parameter could be null |
| * pointer when no fused adjustments are needed. |
| * |
| * Return: 0 on success, errno on failure |
| */ |
| int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg, |
| int *fuse_volt_adjust) |
| { |
| int i, rc; |
| int *volt_adjust, *ro_scale; |
| bool explicit_adjustment, fused_adjustment, is_increasing; |
| |
| explicit_adjustment = of_find_property(vreg->of_node, |
| "qcom,cpr-closed-loop-voltage-adjustment", NULL); |
| fused_adjustment = of_find_property(vreg->of_node, |
| "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL); |
| |
| if (!explicit_adjustment && !fused_adjustment && !vreg->aging_allowed) { |
| /* No adjustment required. */ |
| return 0; |
| } else if (!of_find_property(vreg->of_node, |
| "qcom,cpr-ro-scaling-factor", NULL)) { |
| cpr3_err(vreg, "qcom,cpr-ro-scaling-factor is required for closed-loop voltage adjustment, but is missing\n"); |
| return -EINVAL; |
| } |
| |
| volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust), |
| GFP_KERNEL); |
| ro_scale = kcalloc(vreg->corner_count * CPR3_RO_COUNT, |
| sizeof(*ro_scale), GFP_KERNEL); |
| if (!volt_adjust || !ro_scale) { |
| rc = -ENOMEM; |
| goto done; |
| } |
| |
| rc = cpr3_parse_corner_array_property(vreg, |
| "qcom,cpr-ro-scaling-factor", CPR3_RO_COUNT, ro_scale); |
| if (rc) { |
| cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n", |
| rc); |
| goto done; |
| } |
| |
| for (i = 0; i < vreg->corner_count; i++) |
| memcpy(vreg->corner[i].ro_scale, &ro_scale[i * CPR3_RO_COUNT], |
| sizeof(*ro_scale) * CPR3_RO_COUNT); |
| |
| if (explicit_adjustment) { |
| rc = cpr3_parse_corner_array_property(vreg, |
| "qcom,cpr-closed-loop-voltage-adjustment", |
| 1, volt_adjust); |
| if (rc) { |
| cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n", |
| rc); |
| goto done; |
| } |
| |
| _cpr3_adjust_target_quotients(vreg, volt_adjust, ro_scale, |
| "from DT"); |
| cpr3_enforce_inc_quotient_monotonicity(vreg); |
| } |
| |
| if (fused_adjustment && fuse_volt_adjust) { |
| memset(volt_adjust, 0, |
| sizeof(*volt_adjust) * vreg->corner_count); |
| |
| rc = cpr3_apply_closed_loop_offset_voltages(vreg, volt_adjust, |
| fuse_volt_adjust); |
| if (rc) { |
| cpr3_err(vreg, "could not apply fused closed-loop voltage reductions, rc=%d\n", |
| rc); |
| goto done; |
| } |
| |
| is_increasing = _cpr3_adjust_target_quotients(vreg, volt_adjust, |
| ro_scale, "from fuse"); |
| if (is_increasing) |
| cpr3_enforce_inc_quotient_monotonicity(vreg); |
| else |
| cpr3_enforce_dec_quotient_monotonicity(vreg); |
| } |
| |
| done: |
| kfree(volt_adjust); |
| kfree(ro_scale); |
| return rc; |
| } |