regulator: cpr-regulator: Add CPR de-aging support

CPR aging sensors detect the performance degradation
resulting from silicon aging. The CPR de-aging algorithm
detects the aging degradation of the device by reading
aging sensor data and generating necessary voltage guard band
to maintain SoC performance. Limit the generated voltage
guard band to a pre-defined threshold.

Change-Id: Iea6123c1b42856bb123d3e1c79f60401882aa8ba
Signed-off-by: Ravindranath Thiyagarajan <rthiyaga@codeaurora.org>
Signed-off-by: Tirupathi Reddy <tirupath@codeaurora.org>
This commit is contained in:
Ravindranath Thiyagarajan 2015-07-17 13:03:44 +05:30 committed by Gerrit - the friendly Code Review server
parent 83b6fb9c96
commit 8d74a3c011
2 changed files with 702 additions and 48 deletions

View File

@ -460,9 +460,10 @@ Optional properties:
applied unconditionally.
- qcom,cpr-cpus: Array of CPU phandles which correspond to the cores that this cpr-regulator
device must monitor when adjusting the voltage and/or target quotient based
upon the number of online cores. This property must be specified in order to
upon the number of online cores or make sure that one of them must be online
when performing de-aging measurements. This property must be specified in order to
utilize the qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment or
qcom,cpr-online-cpu-virtual-corner-quotient-adjustment properties.
qcom,cpr-online-cpu-virtual-corner-quotient-adjustment or qcom,cpr-aging-sensor-id properties.
- qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment: Array of tuples where each tuple specifies
the voltage adjustment for each corner. These adjustments apply to the
initial voltage of each corner. The size of each tuple must be equal
@ -655,7 +656,58 @@ Optional properties:
cpr-regulator device or neither must be specified.
- vdd-vsens-corner-supply: Regulator to specify the current operating fuse corner to the Voltage Sensor.
- vdd-vsens-voltage-supply: Regulator to specify the corner floor/ceiling voltages to the Voltage Sensor.
- qcom,cpr-aging-sensor-id: Array of CPR sensor IDs to be used in the CPR de-aging algorithm. The number
of values should be equal to number of sensors selected for age calibration.
If this property is not specified, then the de-aging procedure is not enabled.
- qcom,cpr-de-aging-allowed: Integer values that specify whether the CPR de-aging procedure is allowed or
not for a particular fuse revision. If the qcom,cpr-fuse-version-map
property is specified, then qcom,cpr-de-aging-allowed must contain the same number
of elements as there are tuples in qcom,cpr-fuse-version-map. If qcom,cpr-fuse-version-map
is not specified, then qcom,cpr-de-aging-allowed must contain a single value that
is used unconditionally. An element value of 1 means that the CPR de-aging procedure
can be performed for parts with the corresponding fuse revision. An element value of 0
means that CPR de-aging cannot be performed.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-aging-ref-corner: The vdd-apc-supply reference virtual voltage corner to be set during the CPR de-aging
measurements. This corner value is needed to set appropriate voltage on
the dependent voltage rails such as vdd-mx and mem-acc.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-aging-ref-voltage: The vdd-apc-supply reference voltage in microvolts to be set during the
CPR de-aging measurements.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-max-aging-margin: The maximum allowed aging voltage margin in microvolts. This is used to limit
the calculated aging voltage margin.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-non-collapsible-sensors: Array of CPR sensor IDs which are in non-collapsible domain. The sensor IDs not
specified in the array should be bypassed for the de-aging procedure. The number of
elements should be less than or equal to 32. The values of the array elements should
be greater than or equal to 0 and less than or equal to 31.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-aging-ro-scaling-factor: The aging ring oscillator (RO) scaling factor with units of QUOT/V.
This value is used for calculating a voltage margin from RO measurements.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-ro-scaling-factor: Array of scaling factors with units of QUOT/V for each ring oscillator ordered
from the lowest to the highest RO. These values are used to calculate
the aging voltage margin adjustment for all of the ROs. Since CPR2 supports
exactly 8 ROs, the array must contain 8 elements corresponding to RO0 through RO7 in order.
If a given RO is unused for a fuse corner, then its scaling factor may be specified as 0.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-aging-derate: Array of scaling factors which define the amount of derating to apply to the reference
aging voltage margin adjustment for each of the fuse corners. Each element has units
of uV/mV. This property must be of length defined by qcom,cpr-fuse-corners.
The elements are ordered from the lowest to the highest fuse corner.
This property is required if the qcom,cpr-aging-sensor-id property has been specified.
- qcom,cpr-fuse-aging-init-quot-diff: Array of quadruples in which each quadruple specifies a fuse location to read in
order to get an initial quotient difference. The difference between quot min and quot max
is fused as the initial quotient difference.
The 4 elements in one quadruple are:
[0]: => the fuse row number of the bits
[1]: => LSB bit position of the bits
[2]: => number of the bits
[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
The number of quadruples should be equal to the number of values specified in
the qcom,cpr-aging-sensor-id property. This property is required if
the qcom,cpr-aging-sensor-id property has been specified.
Example:
apc_vreg_corner: regulator@f9018000 {
status = "okay";
@ -889,4 +941,20 @@ Example:
<140 7 3 0>,
<138 45 5 0>;
qcom,cpr-fuse-quot-offset-scale = <5 5 5>;
qcom,cpr-aging-sensor-id = <17, 18>;
qcom,cpr-aging-ref-corner = <4>;
qcom,cpr-aging-ref-voltage = <1050000>;
qcom,cpr-max-aging-margin = <15000>;
qcom,cpr-de-aging-allowed =
<0>,
<0>,
<1>;
qcom,cpr-non-collapsible-sensors= <7 12 17 22>;
qcom,cpr-aging-ro-scaling-factor = <3500>;
qcom,cpr-ro-scaling-factor = <0 2500 2500 2500 0 0 0 0>;
qcom,cpr-aging-derate = <1000 1000 1250>;
qcom,cpr-fuse-aging-init-quot-diff =
<101 0 8 0>,
<101 8 8 0>;
};

View File

@ -15,6 +15,8 @@
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/kernel.h>
@ -49,6 +51,11 @@
#define RBCPR_GCNT_TARGET_GCNT_SHIFT 12
#define RBCPR_GCNT_TARGET_GCNT_MASK ((1<<RBCPR_GCNT_TARGET_GCNT_BITS)-1)
/* RBCPR Sensor Mask and Bypass Registers */
#define REG_RBCPR_SENSOR_MASK0 0x20
#define RBCPR_SENSOR_MASK0_SENSOR(n) (~BIT(n))
#define REG_RBCPR_SENSOR_BYPASS0 0x30
/* RBCPR Timer Control */
#define REG_RBCPR_TIMER_INTERVAL 0x44
#define REG_RBIF_TIMER_ADJUST 0x4C
@ -102,8 +109,13 @@
#define REG_RBIF_CONT_ACK_CMD 0x98
#define REG_RBIF_CONT_NACK_CMD 0x9C
/* RBCPR Result status Register */
/* RBCPR Result status Registers */
#define REG_RBCPR_RESULT_0 0xA0
#define REG_RBCPR_RESULT_1 0xA4
#define RBCPR_RESULT_1_SEL_FAST_BITS 3
#define RBCPR_RESULT_1_SEL_FAST(val) (val & \
((1<<RBCPR_RESULT_1_SEL_FAST_BITS) - 1))
#define RBCPR_RESULT0_BUSY_SHIFT 19
#define RBCPR_RESULT0_BUSY_MASK BIT(RBCPR_RESULT0_BUSY_SHIFT)
@ -134,6 +146,22 @@
#define CPR_NUM_RING_OSC 8
/* RBCPR Debug Resgister */
#define REG_RBCPR_DEBUG1 0x120
#define RBCPR_DEBUG1_QUOT_FAST_BITS 12
#define RBCPR_DEBUG1_QUOT_SLOW_BITS 12
#define RBCPR_DEBUG1_QUOT_SLOW_SHIFT 12
#define RBCPR_DEBUG1_QUOT_FAST(val) (val & \
((1<<RBCPR_DEBUG1_QUOT_FAST_BITS)-1))
#define RBCPR_DEBUG1_QUOT_SLOW(val) ((val>>RBCPR_DEBUG1_QUOT_SLOW_SHIFT) & \
((1<<RBCPR_DEBUG1_QUOT_SLOW_BITS)-1))
/* RBCPR Aging Resgister */
#define REG_RBCPR_HTOL_AGE 0x160
#define RBCPR_HTOL_AGE_PAGE BIT(1)
/* RBCPR Clock Control Register */
#define RBCPR_CLK_SEL_MASK BIT(0)
#define RBCPR_CLK_SEL_19P2_MHZ 0
@ -199,6 +227,28 @@ struct cpr_quot_scale {
u32 multiplier;
};
struct cpr_aging_sensor_info {
u32 sensor_id;
int initial_quot_diff;
int current_quot_diff;
};
struct cpr_aging_info {
struct cpr_aging_sensor_info *sensor_info;
int num_aging_sensors;
int aging_corner;
u32 aging_ro_kv;
u32 *aging_derate;
u32 aging_sensor_bypass;
u32 max_aging_margin;
u32 aging_ref_voltage;
u32 cpr_ro_kv[CPR_NUM_RING_OSC];
int *voltage_adjust;
bool cpr_aging_error;
bool cpr_aging_done;
};
static const char * const vdd_apc_name[] = {"vdd-apc-optional-prim",
"vdd-apc-optional-sec",
"vdd-apc"};
@ -317,9 +367,12 @@ struct cpr_regulator {
int **adj_cpus_open_loop_volt;
bool adj_cpus_open_loop_volt_as_ceiling;
struct notifier_block cpu_notifier;
cpumask_t cpu_mask;
bool is_cpr_suspended;
bool skip_voltage_change_during_suspend;
struct cpr_aging_info *aging_info;
};
#define CPR_DEBUG_MASK_IRQ BIT(0)
@ -1017,6 +1070,178 @@ _exit:
return IRQ_HANDLED;
}
static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
struct cpr_aging_sensor_info *aging_sensor_info)
{
int retries, rc = 0, sel_fast = 0, i;
int age_quot_min, age_quot_max;
u32 val;
/* Clear the target quotient value and gate count of all ROs */
for (i = 0; i < CPR_NUM_RING_OSC; i++)
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0);
/* Program GCNT0/1 for getting aging data */
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), cpr_vreg->gcnt);
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), cpr_vreg->gcnt);
/* Program TIMER_INTERVAL to zero */
cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, 0);
/* Bypass sensors in collapsible domain */
cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0,
(cpr_vreg->aging_info->aging_sensor_bypass &
RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id)));
/* Mask other sensors */
cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0,
RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id));
val = cpr_read(cpr_vreg, REG_RBCPR_SENSOR_MASK0);
cpr_debug(cpr_vreg, "RBCPR_SENSOR_MASK0 = 0x%08x\n", val);
/* Enable cpr controller */
cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, RBCPR_CTL_LOOP_EN);
/* Make sure cpr starts measurement with toggling busy bit */
mb();
/* Wait and Ignore the first measurement. Time-out after 5ms */
retries = 50;
while (retries-- && cpr_ctl_is_busy(cpr_vreg))
udelay(100);
if (retries < 0) {
cpr_err(cpr_vreg, "Aging calibration failed\n");
rc = -EBUSY;
goto _exit;
}
/* Set age page mode and send cont nack */
cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, RBCPR_HTOL_AGE_PAGE);
cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
/* Make sure cpr starts next measurement with toggling busy bit */
mb();
/* Wait for controller to finish measurement and time-out after 5ms */
retries = 50;
while (retries-- && cpr_ctl_is_busy(cpr_vreg))
udelay(100);
if (retries < 0) {
cpr_err(cpr_vreg, "Aging calibration failed\n");
rc = -EBUSY;
goto _exit;
}
val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_1);
sel_fast = RBCPR_RESULT_1_SEL_FAST(val);
cpr_debug(cpr_vreg, "RBCPR_RESULT_1 = 0x%08x\n", val);
val = cpr_read(cpr_vreg, REG_RBCPR_DEBUG1);
cpr_debug(cpr_vreg, "RBCPR_DEBUG1 = 0x%08x\n", val);
if (sel_fast == 1) {
age_quot_min = RBCPR_DEBUG1_QUOT_FAST(val);
age_quot_max = RBCPR_DEBUG1_QUOT_SLOW(val);
} else {
age_quot_min = RBCPR_DEBUG1_QUOT_SLOW(val);
age_quot_max = RBCPR_DEBUG1_QUOT_FAST(val);
}
cpr_debug(cpr_vreg, "Age sensor[%d]: quot_min = %d, quot_max = %d\n",
aging_sensor_info->sensor_id,
age_quot_min, age_quot_max);
aging_sensor_info->current_quot_diff = age_quot_min - age_quot_max;
cpr_debug(cpr_vreg,
"Age sensor[%d]: current aging quot diff = %d\n",
aging_sensor_info->sensor_id,
aging_sensor_info->current_quot_diff);
_exit:
/* Clear age page bit */
cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, 0x0);
/* Disable the CPR controller after aging procedure */
cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0x0);
/* Clear the sensor bypass */
cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0, 0x0);
/* Unmask all sensors */
cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0, 0x0);
/* Clear gcnt0/1 registers */
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), 0x0);
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), 0x0);
/* Program the delay count for the timer */
val = (cpr_vreg->ref_clk_khz * cpr_vreg->timer_delay_us) / 1000;
cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, val);
return rc;
}
static void cpr_de_aging_adjustment(void *data)
{
struct cpr_regulator *cpr_vreg = (struct cpr_regulator *)data;
struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
struct cpr_aging_sensor_info *aging_sensor_info;
int i, num_aging_sensors, retries, rc = 0;
int max_quot_diff = 0, ro_sel = 0;
u32 voltage_adjust, aging_voltage_adjust = 0;
aging_sensor_info = aging_info->sensor_info;
num_aging_sensors = aging_info->num_aging_sensors;
for (i = 0; i < num_aging_sensors; i++, aging_sensor_info++) {
retries = 2;
while (retries--) {
rc = cpr_get_aging_quot_delta(cpr_vreg,
aging_sensor_info);
if (!rc)
break;
}
if (rc && retries < 0) {
cpr_err(cpr_vreg, "error in age calibration: rc = %d\n",
rc);
aging_info->cpr_aging_error = true;
return;
}
max_quot_diff = max(max_quot_diff,
(aging_sensor_info->current_quot_diff -
aging_sensor_info->initial_quot_diff));
}
cpr_debug(cpr_vreg, "Max aging quot delta = %d\n",
max_quot_diff);
aging_voltage_adjust = DIV_ROUND_UP(max_quot_diff * 1000000,
aging_info->aging_ro_kv);
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
/* Remove initial max aging adjustment */
ro_sel = cpr_vreg->cpr_fuse_ro_sel[i];
cpr_vreg->cpr_fuse_target_quot[i] -=
(aging_info->cpr_ro_kv[ro_sel]
* aging_info->max_aging_margin) / 1000000;
aging_info->voltage_adjust[i] = 0;
if (aging_voltage_adjust > 0) {
/* Add required aging adjustment */
voltage_adjust = (aging_voltage_adjust
* aging_info->aging_derate[i]) / 1000;
voltage_adjust = min(voltage_adjust,
aging_info->max_aging_margin);
cpr_vreg->cpr_fuse_target_quot[i] +=
(aging_info->cpr_ro_kv[ro_sel]
* voltage_adjust) / 1000000;
aging_info->voltage_adjust[i] = voltage_adjust;
}
}
}
static int cpr_regulator_is_enabled(struct regulator_dev *rdev)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
@ -1085,11 +1310,69 @@ static int cpr_regulator_disable(struct regulator_dev *rdev)
return rc;
}
static int cpr_calculate_de_aging_margin(struct cpr_regulator *cpr_vreg)
{
struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
enum voltage_change_dir change_dir = NO_CHANGE;
u32 save_ctl, save_irq;
cpumask_t tmp_mask;
int rc = 0, i;
save_ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL);
save_irq = cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line));
/* Disable interrupt and CPR */
cpr_irq_set(cpr_vreg, 0);
cpr_write(cpr_vreg, REG_RBCPR_CTL, 0);
if (aging_info->aging_corner > cpr_vreg->corner)
change_dir = UP;
else if (aging_info->aging_corner < cpr_vreg->corner)
change_dir = DOWN;
/* set selected reference voltage for de-aging */
rc = cpr_scale_voltage(cpr_vreg,
aging_info->aging_corner,
aging_info->aging_ref_voltage,
change_dir);
if (rc) {
cpr_err(cpr_vreg, "Unable to set aging reference voltage, rc = %d\n",
rc);
return rc;
}
get_online_cpus();
cpumask_and(&tmp_mask, &cpr_vreg->cpu_mask, cpu_online_mask);
if (!cpumask_empty(&tmp_mask)) {
smp_call_function_any(&tmp_mask,
cpr_de_aging_adjustment,
cpr_vreg, true);
aging_info->cpr_aging_done = true;
if (!aging_info->cpr_aging_error)
for (i = CPR_FUSE_CORNER_MIN;
i <= cpr_vreg->num_fuse_corners; i++)
cpr_info(cpr_vreg, "Corner[%d]: age adjusted target quot = %d\n",
i, cpr_vreg->cpr_fuse_target_quot[i]);
}
put_online_cpus();
/* Clear interrupts */
cpr_irq_clr(cpr_vreg);
/* Restore register values */
cpr_irq_set(cpr_vreg, save_irq);
cpr_write(cpr_vreg, REG_RBCPR_CTL, save_ctl);
return rc;
}
/* Note that cpr_vreg->cpr_mutex must be held by the caller. */
static int cpr_regulator_set_voltage(struct regulator_dev *rdev,
int corner, bool reset_quot)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
int rc;
int new_volt;
enum voltage_change_dir change_dir = NO_CHANGE;
@ -1110,6 +1393,23 @@ static int cpr_regulator_set_voltage(struct regulator_dev *rdev,
else if (corner < cpr_vreg->corner)
change_dir = DOWN;
/* Read age sensor data and apply de-aging adjustments */
if (cpr_vreg->vreg_enabled && aging_info && !aging_info->cpr_aging_done
&& (corner <= aging_info->aging_corner)) {
rc = cpr_calculate_de_aging_margin(cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "failed in de-aging calibration: rc=%d\n",
rc);
} else {
change_dir = NO_CHANGE;
if (corner > aging_info->aging_corner)
change_dir = UP;
else if (corner < aging_info->aging_corner)
change_dir = DOWN;
}
reset_quot = true;
}
rc = cpr_scale_voltage(cpr_vreg, corner, new_volt, change_dir);
if (rc)
return rc;
@ -3109,6 +3409,307 @@ static int cpr_check_allowed(struct platform_device *pdev,
return rc;
}
static int cpr_check_de_aging_allowed(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
struct device_node *of_node = dev->of_node;
char *allow_str = "qcom,cpr-de-aging-allowed";
int rc = 0, count;
int tuple_count, tuple_match;
u32 allow_status = 0;
if (!of_find_property(of_node, allow_str, &count)) {
/* CPR de-aging is not allowed for all fuse revisions. */
return allow_status;
}
count /= sizeof(u32);
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH)
/* No matching index to use for CPR de-aging allowed. */
return 0;
tuple_count = cpr_vreg->cpr_fuse_map_count;
tuple_match = cpr_vreg->cpr_fuse_map_match;
} else {
tuple_count = 1;
tuple_match = 0;
}
if (count != tuple_count) {
cpr_err(cpr_vreg, "%s count=%d is invalid\n", allow_str,
count);
return -EINVAL;
}
rc = of_property_read_u32_index(of_node, allow_str, tuple_match,
&allow_status);
if (rc) {
cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
allow_str, tuple_match, rc);
return rc;
}
cpr_info(cpr_vreg, "CPR de-aging is %s for fuse revision %d\n",
allow_status ? "allowed" : "not allowed",
cpr_vreg->cpr_fuse_revision);
return allow_status;
}
static int cpr_aging_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
struct cpr_aging_info *aging_info;
struct cpr_aging_sensor_info *sensor_info;
int num_fuse_corners = cpr_vreg->num_fuse_corners;
int i, rc = 0, len = 0, num_aging_sensors, ro_sel, bits;
u32 *aging_sensor_id, *fuse_sel, *fuse_sel_orig;
u32 sensor = 0, non_collapsible_sensor_mask = 0;
u64 efuse_val;
struct property *prop;
if (!of_find_property(of_node, "qcom,cpr-aging-sensor-id", &len)) {
/* No CPR de-aging adjustments needed */
return 0;
}
if (len == 0) {
cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property format is invalid\n");
return -EINVAL;
}
num_aging_sensors = len / sizeof(u32);
cpr_debug(cpr_vreg, "No of aging sensors = %d\n", num_aging_sensors);
if (cpumask_empty(&cpr_vreg->cpu_mask)) {
cpr_err(cpr_vreg, "qcom,cpr-cpus property missing\n");
return -EINVAL;
}
rc = cpr_check_de_aging_allowed(cpr_vreg, &pdev->dev);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr_check_de_aging_allowed failed: rc=%d\n",
rc);
return rc;
} else if (rc == 0) {
/* CPR de-aging is not allowed for the current fuse combo */
return 0;
}
aging_info = devm_kzalloc(&pdev->dev, sizeof(*aging_info),
GFP_KERNEL);
if (!aging_info)
return -ENOMEM;
cpr_vreg->aging_info = aging_info;
aging_info->num_aging_sensors = num_aging_sensors;
rc = of_property_read_u32(of_node, "qcom,cpr-aging-ref-corner",
&aging_info->aging_corner);
if (rc) {
cpr_err(cpr_vreg, "qcom,cpr-aging-ref-corner missing rc=%d\n",
rc);
return rc;
}
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ref-voltage",
&aging_info->aging_ref_voltage, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-max-aging-margin",
&aging_info->max_aging_margin, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ro-scaling-factor",
&aging_info->aging_ro_kv, rc);
if (rc)
return rc;
/* Check for DIV by 0 error */
if (aging_info->aging_ro_kv == 0) {
cpr_err(cpr_vreg, "invalid cpr-aging-ro-scaling-factor value: %u\n",
aging_info->aging_ro_kv);
return -EINVAL;
}
rc = of_property_read_u32_array(of_node, "qcom,cpr-ro-scaling-factor",
aging_info->cpr_ro_kv, CPR_NUM_RING_OSC);
if (rc) {
cpr_err(cpr_vreg, "qcom,cpr-ro-scaling-factor property read failed, rc = %d\n",
rc);
return rc;
}
prop = of_find_property(of_node, "qcom,cpr-non-collapsible-sensors",
&len);
len = len / sizeof(u32);
if ((!prop) || len < 0 || len > 32) {
cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors is missing or has an incorrect size\n");
return -EINVAL;
}
for (i = 0; i < len; i++) {
rc = of_property_read_u32_index(of_node,
"qcom,cpr-non-collapsible-sensors",
i, &sensor);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-non-collapsible-sensors index %u, rc=%d\n",
i, rc);
return rc;
}
if (sensor > 31) {
cpr_err(cpr_vreg, "invalid non-collapsible sensor = %u\n",
sensor);
return -EINVAL;
}
non_collapsible_sensor_mask |= BIT(sensor);
}
/* Bypass the sensors in collapsible domain for de-aging measurements */
aging_info->aging_sensor_bypass = ~(non_collapsible_sensor_mask);
cpr_info(cpr_vreg, "sensor bypass mask for aging = 0x%08x\n",
aging_info->aging_sensor_bypass);
prop = of_find_property(pdev->dev.of_node, "qcom,cpr-aging-derate",
NULL);
if ((!prop) ||
(prop->length != num_fuse_corners * sizeof(u32))) {
cpr_err(cpr_vreg, "qcom,cpr-aging-derate incorrectly configured\n");
return -EINVAL;
}
aging_sensor_id = kcalloc(num_aging_sensors, sizeof(*aging_sensor_id),
GFP_KERNEL);
fuse_sel = kcalloc(num_aging_sensors * 4, sizeof(*fuse_sel),
GFP_KERNEL);
aging_info->voltage_adjust = devm_kcalloc(&pdev->dev,
num_fuse_corners + 1,
sizeof(*aging_info->voltage_adjust),
GFP_KERNEL);
aging_info->sensor_info = devm_kcalloc(&pdev->dev, num_aging_sensors,
sizeof(*aging_info->sensor_info),
GFP_KERNEL);
aging_info->aging_derate = devm_kcalloc(&pdev->dev,
num_fuse_corners + 1,
sizeof(*aging_info->aging_derate),
GFP_KERNEL);
if (!aging_info->aging_derate || !aging_sensor_id
|| !aging_info->sensor_info || !fuse_sel
|| !aging_info->voltage_adjust)
goto err;
rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-sensor-id",
aging_sensor_id, num_aging_sensors);
if (rc) {
cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property read failed, rc = %d\n",
rc);
goto err;
}
for (i = 0; i < num_aging_sensors; i++)
if (aging_sensor_id[i] < 0 || aging_sensor_id[i] > 31) {
cpr_err(cpr_vreg, "Invalid aging sensor id: %u\n",
aging_sensor_id[i]);
rc = -EINVAL;
goto err;
}
rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-derate",
&aging_info->aging_derate[CPR_FUSE_CORNER_MIN],
num_fuse_corners);
if (rc) {
cpr_err(cpr_vreg, "qcom,cpr-aging-derate property read failed, rc = %d\n",
rc);
goto err;
}
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-aging-init-quot-diff",
fuse_sel, (num_aging_sensors * 4));
if (rc) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-aging-init-quot-diff read failed, rc = %d\n",
rc);
goto err;
}
fuse_sel_orig = fuse_sel;
sensor_info = aging_info->sensor_info;
for (i = 0; i < num_aging_sensors; i++, sensor_info++) {
sensor_info->sensor_id = aging_sensor_id[i];
efuse_val = cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
fuse_sel[1], fuse_sel[2], fuse_sel[3]);
bits = fuse_sel[2];
sensor_info->initial_quot_diff = ((efuse_val & BIT(bits - 1)) ?
-1 : 1) * (efuse_val & (BIT(bits - 1) - 1));
cpr_debug(cpr_vreg, "Age sensor[%d] Initial quot diff = %d\n",
sensor_info->sensor_id,
sensor_info->initial_quot_diff);
fuse_sel += 4;
}
/*
* Add max aging margin here. This can be adjusted later in
* de-aging algorithm.
*/
for (i = CPR_FUSE_CORNER_MIN; i <= num_fuse_corners; i++) {
ro_sel = cpr_vreg->cpr_fuse_ro_sel[i];
cpr_vreg->cpr_fuse_target_quot[i] +=
(aging_info->cpr_ro_kv[ro_sel]
* aging_info->max_aging_margin) / 1000000;
aging_info->voltage_adjust[i] = aging_info->max_aging_margin;
cpr_info(cpr_vreg, "Corner[%d]: age margin adjusted quotient = %d\n",
i, cpr_vreg->cpr_fuse_target_quot[i]);
}
err:
kfree(fuse_sel_orig);
kfree(aging_sensor_id);
return rc;
}
static int cpr_cpu_map_init(struct cpr_regulator *cpr_vreg, struct device *dev)
{
struct device_node *cpu_node;
int i, cpu;
if (!of_find_property(dev->of_node, "qcom,cpr-cpus",
&cpr_vreg->num_adj_cpus)) {
/* No adjustments based on online cores */
return 0;
}
cpr_vreg->num_adj_cpus /= sizeof(u32);
cpr_vreg->adj_cpus = devm_kcalloc(dev, cpr_vreg->num_adj_cpus,
sizeof(int), GFP_KERNEL);
if (!cpr_vreg->adj_cpus)
return -ENOMEM;
for (i = 0; i < cpr_vreg->num_adj_cpus; i++) {
cpu_node = of_parse_phandle(dev->of_node, "qcom,cpr-cpus", i);
if (!cpu_node) {
cpr_err(cpr_vreg, "could not find CPU node %d\n", i);
return -EINVAL;
}
cpr_vreg->adj_cpus[i] = -1;
for_each_possible_cpu(cpu) {
if (of_get_cpu_node(cpu, NULL) == cpu_node) {
cpr_vreg->adj_cpus[i] = cpu;
cpumask_set_cpu(cpu, &cpr_vreg->cpu_mask);
break;
}
}
of_node_put(cpu_node);
}
return 0;
}
static int cpr_init_cpr_efuse(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
@ -3313,6 +3914,18 @@ static int cpr_init_cpr_efuse(struct platform_device *pdev,
cpr_vreg->cpr_fuse_target_quot[i]);
}
rc = cpr_cpu_map_init(cpr_vreg, &pdev->dev);
if (rc) {
cpr_err(cpr_vreg, "CPR cpu map init failed: rc=%d\n", rc);
goto error;
}
rc = cpr_aging_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "CPR aging init failed: rc=%d\n", rc);
goto error;
}
rc = cpr_adjust_target_quots(pdev, cpr_vreg);
if (rc)
goto error;
@ -4050,15 +4663,7 @@ done:
static int cpr_init_per_cpu_adjustments(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
struct device_node *cpu_node;
int rc, i, j, cpu;
if (!of_find_property(dev->of_node, "qcom,cpr-cpus",
&cpr_vreg->num_adj_cpus)) {
/* No per-online CPU adjustment needed */
return 0;
}
cpr_vreg->num_adj_cpus /= sizeof(u32);
int rc, i, j;
if (!of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
@ -4066,34 +4671,15 @@ static int cpr_init_per_cpu_adjustments(struct cpr_regulator *cpr_vreg,
&& !of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
NULL)) {
cpr_err(cpr_vreg, "qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment and/or qcom,cpr-online-cpu-virtual-corner-quotient-adjustment must be specified\n");
/* No per-online CPU adjustment needed */
return 0;
}
if (!cpr_vreg->num_adj_cpus) {
cpr_err(cpr_vreg, "qcom,cpr-cpus property missing\n");
return -EINVAL;
}
cpr_vreg->adj_cpus = devm_kzalloc(dev,
sizeof(int) * cpr_vreg->num_adj_cpus,
GFP_KERNEL);
if (!cpr_vreg->adj_cpus) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
return -ENOMEM;
}
for (i = 0; i < cpr_vreg->num_adj_cpus; i++) {
cpu_node = of_parse_phandle(dev->of_node, "qcom,cpr-cpus", i);
if (!cpu_node) {
cpr_err(cpr_vreg, "could not find CPU node %d\n", i);
return -EINVAL;
}
cpr_vreg->adj_cpus[i] = -1;
for_each_possible_cpu(cpu) {
if (of_get_cpu_node(cpu, NULL) == cpu_node) {
cpr_vreg->adj_cpus[i] = cpu;
break;
}
}
of_node_put(cpu_node);
}
rc = cpr_parse_adj_cpus_init_voltage(cpr_vreg, dev);
if (rc) {
cpr_err(cpr_vreg, "cpr_parse_adj_cpus_init_voltage failed: rc =%d\n",
@ -4313,10 +4899,6 @@ static int cpr_init_cpr(struct platform_device *pdev,
if (res && res->start)
cpr_vreg->rbcpr_clk_addr = res->start;
rc = cpr_init_cpr_efuse(pdev, cpr_vreg);
if (rc)
return rc;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr");
if (!res || !res->start) {
cpr_err(cpr_vreg, "missing rbcpr address: res=%p\n", res);
@ -4325,6 +4907,15 @@ static int cpr_init_cpr(struct platform_device *pdev,
cpr_vreg->rbcpr_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
/* Init CPR configuration parameters */
rc = cpr_init_cpr_parameters(pdev, cpr_vreg);
if (rc)
return rc;
rc = cpr_init_cpr_efuse(pdev, cpr_vreg);
if (rc)
return rc;
/* Load per corner ceiling and floor voltages if they exist. */
rc = cpr_init_ceiling_floor_override_voltages(cpr_vreg, &pdev->dev);
if (rc)
@ -4361,11 +4952,6 @@ static int cpr_init_cpr(struct platform_device *pdev,
if (rc)
return rc;
/* Init CPR configuration parameters */
rc = cpr_init_cpr_parameters(pdev, cpr_vreg);
if (rc)
return rc;
/* Get and Init interrupt */
cpr_vreg->cpr_irq = platform_get_irq(pdev, 0);
if (!cpr_vreg->cpr_irq) {
@ -5222,7 +5808,7 @@ static int cpr_regulator_remove(struct platform_device *pdev)
list_del(&cpr_vreg->list);
mutex_unlock(&cpr_regulator_list_mutex);
if (cpr_vreg->adj_cpus)
if (cpr_vreg->cpu_notifier.notifier_call)
unregister_hotcpu_notifier(&cpr_vreg->cpu_notifier);
cpr_apc_exit(cpr_vreg);