diff --git a/drivers/regulator/cpr3-regulator.c b/drivers/regulator/cpr3-regulator.c index 23ac83eb30df..0df2b80ceca5 100644 --- a/drivers/regulator/cpr3-regulator.c +++ b/drivers/regulator/cpr3-regulator.c @@ -3691,14 +3691,16 @@ static void cpr3_regulator_readjust_volt_and_quot(struct cpr3_regulator *vreg, static void cpr3_regulator_set_aging_ref_adjustment( struct cpr3_controller *ctrl, int ref_adjust_volt) { + struct cpr3_regulator *vreg; int i, j; for (i = 0; i < ctrl->thread_count; i++) { for (j = 0; j < ctrl->thread[i].vreg_count; j++) { - cpr3_regulator_readjust_volt_and_quot( - &ctrl->thread[i].vreg[j], - ctrl->aging_ref_adjust_volt, - ref_adjust_volt); + vreg = &ctrl->thread[i].vreg[j]; + cpr3_regulator_readjust_volt_and_quot(vreg, + ctrl->aging_ref_adjust_volt, ref_adjust_volt); + if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) + cprh_adjust_voltages_for_apm(vreg); } } diff --git a/drivers/regulator/cpr3-regulator.h b/drivers/regulator/cpr3-regulator.h index 8897def3ef76..ac571271b0d5 100644 --- a/drivers/regulator/cpr3-regulator.h +++ b/drivers/regulator/cpr3-regulator.h @@ -875,6 +875,7 @@ int cpr4_parse_core_count_temp_voltage_adj(struct cpr3_regulator *vreg, bool use_corner_band); int cpr3_apm_init(struct cpr3_controller *ctrl); int cpr3_mem_acc_init(struct cpr3_regulator *vreg); +void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg); #else @@ -1047,6 +1048,10 @@ static inline int cpr3_mem_acc_init(struct cpr3_regulator *vreg) return 0; } +static inline void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg) +{ +} + #endif /* CONFIG_REGULATOR_CPR3 */ #endif /* __REGULATOR_CPR_REGULATOR_H__ */ diff --git a/drivers/regulator/cpr3-util.c b/drivers/regulator/cpr3-util.c index e3e05b6ad352..c377a65a6393 100644 --- a/drivers/regulator/cpr3-util.c +++ b/drivers/regulator/cpr3-util.c @@ -2008,3 +2008,78 @@ done: 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); + } +} diff --git a/drivers/regulator/cprh-kbss-regulator.c b/drivers/regulator/cprh-kbss-regulator.c index 284180b0e72f..953ea5f33f40 100644 --- a/drivers/regulator/cprh-kbss-regulator.c +++ b/drivers/regulator/cprh-kbss-regulator.c @@ -54,6 +54,8 @@ * @force_highest_corner: Flag indicating that all corners must operate * at the voltage of the highest corner. This is * applicable to MSMCOBALT only. + * @aging_init_quot_diff: Initial quotient difference between CPR aging + * min and max sensors measured at time of manufacturing * * This struct holds the values for all of the fuses read from memory. */ @@ -65,6 +67,7 @@ struct cprh_msmcobalt_kbss_fuses { u64 speed_bin; u64 cpr_fusing_rev; u64 force_highest_corner; + u64 aging_init_quot_diff; }; /* @@ -192,6 +195,18 @@ msmcobalt_cpr_force_highest_corner_param[] = { {}, }; +static const struct cpr3_fuse_param +msmcobalt_kbss_aging_init_quot_diff_param[2][2] = { + [MSMCOBALT_KBSS_POWER_CLUSTER_ID] = { + {69, 6, 13}, + {}, + }, + [MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID] = { + {71, 25, 32}, + {}, + }, +}; + /* * Open loop voltage fuse reference voltages in microvolts for MSMCOBALT v1 */ @@ -225,6 +240,8 @@ msmcobalt_v2_kbss_fuse_ref_volt[2][MSMCOBALT_KBSS_FUSE_CORNERS] = { #define MSMCOBALT_KBSS_FUSE_STEP_VOLT 10000 #define MSMCOBALT_KBSS_VOLTAGE_FUSE_SIZE 6 #define MSMCOBALT_KBSS_QUOT_OFFSET_SCALE 5 +#define MSMCOBALT_KBSS_AGING_INIT_QUOT_DIFF_SIZE 8 +#define MSMCOBALT_KBSS_AGING_INIT_QUOT_DIFF_SCALE 1 #define MSMCOBALT_KBSS_POWER_CPR_SENSOR_COUNT 6 #define MSMCOBALT_KBSS_PERFORMANCE_CPR_SENSOR_COUNT 9 @@ -242,6 +259,12 @@ msmcobalt_v2_kbss_fuse_ref_volt[2][MSMCOBALT_KBSS_FUSE_CORNERS] = { #define MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START 6 #define MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END 11 +#define MSMCOBALT_KBSS_POWER_AGING_SENSOR_ID 0 +#define MSMCOBALT_KBSS_POWER_AGING_BYPASS_MASK0 0 + +#define MSMCOBALT_KBSS_PERFORMANCE_AGING_SENSOR_ID 0 +#define MSMCOBALT_KBSS_PERFORMANCE_AGING_BYPASS_MASK0 0 + /** * cprh_msmcobalt_kbss_read_fuse_data() - load KBSS specific fuse parameter values * @vreg: Pointer to the CPR3 regulator @@ -320,6 +343,15 @@ static int cprh_msmcobalt_kbss_read_fuse_data(struct cpr3_regulator *vreg) } + rc = cpr3_read_fuse_param(base, + msmcobalt_kbss_aging_init_quot_diff_param[id], + &fuse->aging_init_quot_diff); + if (rc) { + cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n", + rc); + return rc; + } + rc = cpr3_read_fuse_param(base, msmcobalt_cpr_force_highest_corner_param, &fuse->force_highest_corner); @@ -826,85 +858,13 @@ static int cprh_kbss_apm_crossover_as_corner(struct cpr3_regulator *vreg) corner->floor_volt = ctrl->apm_crossover_volt; corner->ceiling_volt = ctrl->apm_crossover_volt; corner->open_loop_volt = ctrl->apm_crossover_volt; + corner->abs_ceiling_volt = ctrl->apm_crossover_volt; corner->use_open_loop = true; vreg->corner_count++; return 0; } -/** - * cprh_kbss_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 KBSS 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 in the case that - * floor < threshold <= ceiling: - * if open_loop >= threshold, then floor = threshold - adj - * else ceiling = threshold - step - * where adj = APM hysteresis voltage established to minimize number - * of corners with artificially increased floor voltages - * and 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: 0 on success, errno on failure - */ -static int cprh_kbss_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 0; - } - - 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; - } - - 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); - } - - return 0; -} - /** * cprh_msmcobalt_kbss_set_no_interpolation_quotients() - use the fused target * quotient values for lower frequencies. @@ -1235,12 +1195,7 @@ static int cprh_kbss_init_regulator(struct cpr3_regulator *vreg) return rc; } - rc = cprh_kbss_adjust_voltages_for_apm(vreg); - if (rc) { - cpr3_err(vreg, "unable to adjust voltages for APM\n, rc=%d\n", - rc); - return rc; - } + cprh_adjust_voltages_for_apm(vreg); cpr3_open_loop_voltage_as_ceiling(vreg); @@ -1298,6 +1253,80 @@ static int cprh_kbss_init_regulator(struct cpr3_regulator *vreg) return 0; } +/** + * cprh_kbss_init_aging() - perform KBSS CPRh controller specific aging + * initializations + * @ctrl: Pointer to the CPR3 controller + * + * Return: 0 on success, errno on failure + */ +static int cprh_kbss_init_aging(struct cpr3_controller *ctrl) +{ + struct cprh_msmcobalt_kbss_fuses *fuse = NULL; + struct cpr3_regulator *vreg; + u32 aging_ro_scale; + int i, j, rc; + + for (i = 0; i < ctrl->thread_count; i++) { + for (j = 0; j < ctrl->thread[i].vreg_count; j++) { + if (ctrl->thread[i].vreg[j].aging_allowed) { + ctrl->aging_required = true; + vreg = &ctrl->thread[i].vreg[j]; + fuse = vreg->platform_fuses; + break; + } + } + } + + if (!ctrl->aging_required || !fuse || !vreg) + return 0; + + rc = cpr3_parse_array_property(vreg, "qcom,cpr-aging-ro-scaling-factor", + 1, &aging_ro_scale); + if (rc) + return rc; + + if (aging_ro_scale == 0) { + cpr3_err(ctrl, "aging RO scaling factor is invalid: %u\n", + aging_ro_scale); + return -EINVAL; + } + + ctrl->aging_vdd_mode = REGULATOR_MODE_NORMAL; + ctrl->aging_complete_vdd_mode = REGULATOR_MODE_IDLE; + + ctrl->aging_sensor_count = 1; + ctrl->aging_sensor = kzalloc(sizeof(*ctrl->aging_sensor), GFP_KERNEL); + if (!ctrl->aging_sensor) + return -ENOMEM; + + if (ctrl->ctrl_id == MSMCOBALT_KBSS_POWER_CLUSTER_ID) { + ctrl->aging_sensor->sensor_id + = MSMCOBALT_KBSS_POWER_AGING_SENSOR_ID; + ctrl->aging_sensor->bypass_mask[0] + = MSMCOBALT_KBSS_POWER_AGING_BYPASS_MASK0; + } else { + ctrl->aging_sensor->sensor_id + = MSMCOBALT_KBSS_PERFORMANCE_AGING_SENSOR_ID; + ctrl->aging_sensor->bypass_mask[0] + = MSMCOBALT_KBSS_PERFORMANCE_AGING_BYPASS_MASK0; + } + ctrl->aging_sensor->ro_scale = aging_ro_scale; + + ctrl->aging_sensor->init_quot_diff + = cpr3_convert_open_loop_voltage_fuse(0, + MSMCOBALT_KBSS_AGING_INIT_QUOT_DIFF_SCALE, + fuse->aging_init_quot_diff, + MSMCOBALT_KBSS_AGING_INIT_QUOT_DIFF_SIZE); + + cpr3_debug(ctrl, "sensor %u aging init quotient diff = %d, aging RO scale = %u QUOT/V\n", + ctrl->aging_sensor->sensor_id, + ctrl->aging_sensor->init_quot_diff, + ctrl->aging_sensor->ro_scale); + + return 0; +} + /** * cprh_kbss_init_controller() - perform KBSS CPRh controller specific * initializations @@ -1566,6 +1595,13 @@ static int cprh_kbss_regulator_probe(struct platform_device *pdev) return rc; } + rc = cprh_kbss_init_aging(ctrl); + if (rc) { + cpr3_err(ctrl, "failed to initialize aging configurations, rc=%d\n", + rc); + return rc; + } + platform_set_drvdata(pdev, ctrl); rc = cprh_kbss_populate_opp_table(ctrl);