android_kernel_samsung_msm8976/drivers/regulator/cpr-regulator.c

6287 lines
172 KiB
C
Raw Permalink Normal View History

/*
* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#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>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/sort.h>
#include <linux/uaccess.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/cpr-regulator.h>
#include <linux/msm_thermal.h>
#include <linux/msm_tsens.h>
#include <soc/qcom/scm.h>
/* Register Offsets for RB-CPR and Bit Definitions */
/* RBCPR Version Register */
#define REG_RBCPR_VERSION 0
#define RBCPR_VER_2 0x02
/* RBCPR Gate Count and Target Registers */
#define REG_RBCPR_GCNT_TARGET(n) (0x60 + 4 * n)
#define RBCPR_GCNT_TARGET_GCNT_BITS 10
#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
#define RBIF_TIMER_ADJ_CONS_UP_BITS 4
#define RBIF_TIMER_ADJ_CONS_UP_MASK ((1<<RBIF_TIMER_ADJ_CONS_UP_BITS)-1)
#define RBIF_TIMER_ADJ_CONS_DOWN_BITS 4
#define RBIF_TIMER_ADJ_CONS_DOWN_MASK ((1<<RBIF_TIMER_ADJ_CONS_DOWN_BITS)-1)
#define RBIF_TIMER_ADJ_CONS_DOWN_SHIFT 4
#define RBIF_TIMER_ADJ_CLAMP_INT_BITS 8
#define RBIF_TIMER_ADJ_CLAMP_INT_MASK ((1<<RBIF_TIMER_ADJ_CLAMP_INT_BITS)-1)
#define RBIF_TIMER_ADJ_CLAMP_INT_SHIFT 8
/* RBCPR Config Register */
#define REG_RBIF_LIMIT 0x48
#define REG_RBCPR_STEP_QUOT 0x80
#define REG_RBIF_SW_VLEVEL 0x94
#define RBIF_LIMIT_CEILING_BITS 6
#define RBIF_LIMIT_CEILING_MASK ((1<<RBIF_LIMIT_CEILING_BITS)-1)
#define RBIF_LIMIT_CEILING_SHIFT 6
#define RBIF_LIMIT_FLOOR_BITS 6
#define RBIF_LIMIT_FLOOR_MASK ((1<<RBIF_LIMIT_FLOOR_BITS)-1)
#define RBIF_LIMIT_CEILING_DEFAULT RBIF_LIMIT_CEILING_MASK
#define RBIF_LIMIT_FLOOR_DEFAULT 0
#define RBIF_SW_VLEVEL_DEFAULT 0x20
#define RBCPR_STEP_QUOT_STEPQUOT_BITS 8
#define RBCPR_STEP_QUOT_STEPQUOT_MASK ((1<<RBCPR_STEP_QUOT_STEPQUOT_BITS)-1)
#define RBCPR_STEP_QUOT_IDLE_CLK_BITS 4
#define RBCPR_STEP_QUOT_IDLE_CLK_MASK ((1<<RBCPR_STEP_QUOT_IDLE_CLK_BITS)-1)
#define RBCPR_STEP_QUOT_IDLE_CLK_SHIFT 8
/* RBCPR Control Register */
#define REG_RBCPR_CTL 0x90
#define RBCPR_CTL_LOOP_EN BIT(0)
#define RBCPR_CTL_TIMER_EN BIT(3)
#define RBCPR_CTL_SW_AUTO_CONT_ACK_EN BIT(5)
#define RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN BIT(6)
#define RBCPR_CTL_COUNT_MODE BIT(10)
#define RBCPR_CTL_UP_THRESHOLD_BITS 4
#define RBCPR_CTL_UP_THRESHOLD_MASK ((1<<RBCPR_CTL_UP_THRESHOLD_BITS)-1)
#define RBCPR_CTL_UP_THRESHOLD_SHIFT 24
#define RBCPR_CTL_DN_THRESHOLD_BITS 4
#define RBCPR_CTL_DN_THRESHOLD_MASK ((1<<RBCPR_CTL_DN_THRESHOLD_BITS)-1)
#define RBCPR_CTL_DN_THRESHOLD_SHIFT 28
/* RBCPR Ack/Nack Response */
#define REG_RBIF_CONT_ACK_CMD 0x98
#define REG_RBIF_CONT_NACK_CMD 0x9C
/* 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)
#define RBCPR_RESULT0_ERROR_LT0_SHIFT 18
#define RBCPR_RESULT0_ERROR_SHIFT 6
#define RBCPR_RESULT0_ERROR_BITS 12
#define RBCPR_RESULT0_ERROR_MASK ((1<<RBCPR_RESULT0_ERROR_BITS)-1)
#define RBCPR_RESULT0_ERROR_STEPS_SHIFT 2
#define RBCPR_RESULT0_ERROR_STEPS_BITS 4
#define RBCPR_RESULT0_ERROR_STEPS_MASK ((1<<RBCPR_RESULT0_ERROR_STEPS_BITS)-1)
#define RBCPR_RESULT0_STEP_UP_SHIFT 1
/* RBCPR Interrupt Control Register */
#define REG_RBIF_IRQ_EN(n) (0x100 + 4 * n)
#define REG_RBIF_IRQ_CLEAR 0x110
#define REG_RBIF_IRQ_STATUS 0x114
#define CPR_INT_DONE BIT(0)
#define CPR_INT_MIN BIT(1)
#define CPR_INT_DOWN BIT(2)
#define CPR_INT_MID BIT(3)
#define CPR_INT_UP BIT(4)
#define CPR_INT_MAX BIT(5)
#define CPR_INT_CLAMP BIT(6)
#define CPR_INT_ALL (CPR_INT_DONE | CPR_INT_MIN | CPR_INT_DOWN | \
CPR_INT_MID | CPR_INT_UP | CPR_INT_MAX | CPR_INT_CLAMP)
#define CPR_INT_DEFAULT (CPR_INT_UP | CPR_INT_DOWN)
#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)
#define RBCPR_AGE_DATA_STATUS BIT(2)
/* RBCPR Clock Control Register */
#define RBCPR_CLK_SEL_MASK BIT(0)
#define RBCPR_CLK_SEL_19P2_MHZ 0
#define RBCPR_CLK_SEL_AHB_CLK BIT(0)
/* CPR eFuse parameters */
#define CPR_FUSE_TARGET_QUOT_BITS 12
#define CPR_FUSE_TARGET_QUOT_BITS_MASK ((1<<CPR_FUSE_TARGET_QUOT_BITS)-1)
#define CPR_FUSE_RO_SEL_BITS 3
#define CPR_FUSE_RO_SEL_BITS_MASK ((1<<CPR_FUSE_RO_SEL_BITS)-1)
#define CPR_FUSE_MIN_QUOT_DIFF 50
#define BYTES_PER_FUSE_ROW 8
#define SPEED_BIN_NONE UINT_MAX
#define FUSE_REVISION_UNKNOWN (-1)
#define FUSE_MAP_NO_MATCH (-1)
#define FUSE_PARAM_MATCH_ANY 0xFFFFFFFF
#define FLAGS_IGNORE_1ST_IRQ_STATUS BIT(0)
#define FLAGS_SET_MIN_VOLTAGE BIT(1)
#define FLAGS_UPLIFT_QUOT_VOLT BIT(2)
/*
* The number of individual aging measurements to perform which are then
* averaged together in order to determine the final aging adjustment value.
*/
#define CPR_AGING_MEASUREMENT_ITERATIONS 16
/*
* Aging measurements for the aged and unaged ring oscillators take place a few
* microseconds apart. If the vdd-supply voltage fluctuates between the two
* measurements, then the difference between them will be incorrect. The
* difference could end up too high or too low. This constant defines the
* number of lowest and highest measurements to ignore when averaging.
*/
#define CPR_AGING_MEASUREMENT_FILTER 3
#define CPR_REGULATOR_DRIVER_NAME "qcom,cpr-regulator"
/**
* enum vdd_mx_vmin_method - Method to determine vmin for vdd-mx
* %VDD_MX_VMIN_APC: Equal to APC voltage
* %VDD_MX_VMIN_APC_CORNER_CEILING: Equal to PVS corner ceiling voltage
* %VDD_MX_VMIN_APC_SLOW_CORNER_CEILING:
* Equal to slow speed corner ceiling
* %VDD_MX_VMIN_MX_VMAX: Equal to specified vdd-mx-vmax voltage
* %VDD_MX_VMIN_APC_CORNER_MAP: Equal to the APC corner mapped MX
* voltage
*/
enum vdd_mx_vmin_method {
VDD_MX_VMIN_APC,
VDD_MX_VMIN_APC_CORNER_CEILING,
VDD_MX_VMIN_APC_SLOW_CORNER_CEILING,
VDD_MX_VMIN_MX_VMAX,
VDD_MX_VMIN_APC_FUSE_CORNER_MAP,
VDD_MX_VMIN_APC_CORNER_MAP,
};
#define CPR_CORNER_MIN 1
#define CPR_FUSE_CORNER_MIN 1
/*
* This is an arbitrary upper limit which is used in a sanity check in order to
* avoid excessive memory allocation due to bad device tree data.
*/
#define CPR_FUSE_CORNER_LIMIT 100
struct quot_adjust_info {
int speed_bin;
int virtual_corner;
int quot_adjust;
};
struct cpr_quot_scale {
u32 offset;
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"};
enum voltage_change_dir {
NO_CHANGE,
DOWN,
UP,
};
struct cpr_regulator {
struct list_head list;
struct regulator_desc rdesc;
struct regulator_dev *rdev;
bool vreg_enabled;
int corner;
int ceiling_max;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
struct dentry *debugfs;
struct device *dev;
/* eFuse parameters */
phys_addr_t efuse_addr;
void __iomem *efuse_base;
u64 *remapped_row;
u32 remapped_row_base;
int num_remapped_rows;
/* Process voltage parameters */
u32 *pvs_corner_v;
/* Process voltage variables */
u32 pvs_bin;
u32 speed_bin;
u32 pvs_version;
/* APC voltage regulator */
struct regulator *vdd_apc;
/* Dependency parameters */
struct regulator *vdd_mx;
int vdd_mx_vmax;
int vdd_mx_vmin_method;
int vdd_mx_vmin;
int *vdd_mx_corner_map;
struct regulator *rpm_apc_vreg;
int *rpm_apc_corner_map;
/* mem-acc regulator */
struct regulator *mem_acc_vreg;
/* thermal monitor */
int tsens_id;
int cpr_disable_temp_threshold;
int cpr_enable_temp_threshold;
bool cpr_disable_on_temperature;
bool cpr_thermal_disable;
struct threshold_info tsens_threshold_config;
/* CPR parameters */
u32 num_fuse_corners;
u64 cpr_fuse_bits;
bool cpr_fuse_disable;
bool cpr_fuse_local;
bool cpr_fuse_redundant;
int cpr_fuse_revision;
int cpr_fuse_map_count;
int cpr_fuse_map_match;
int *cpr_fuse_target_quot;
int *cpr_fuse_ro_sel;
int *fuse_quot_offset;
int gcnt;
unsigned int cpr_irq;
void __iomem *rbcpr_base;
phys_addr_t rbcpr_clk_addr;
struct mutex cpr_mutex;
int *cpr_max_ceiling;
int *ceiling_volt;
int *floor_volt;
int *fuse_ceiling_volt;
int *fuse_floor_volt;
int *last_volt;
int *open_loop_volt;
int step_volt;
int *save_ctl;
int *save_irq;
int *vsens_corner_map;
/* vsens status */
bool vsens_enabled;
/* vsens regulators */
struct regulator *vdd_vsens_corner;
struct regulator *vdd_vsens_voltage;
/* Config parameters */
bool enable;
u32 ref_clk_khz;
u32 timer_delay_us;
u32 timer_cons_up;
u32 timer_cons_down;
u32 irq_line;
u32 *step_quotient;
u32 up_threshold;
u32 down_threshold;
u32 idle_clocks;
u32 gcnt_time_us;
u32 clamp_timer_interval;
u32 vdd_apc_step_up_limit;
u32 vdd_apc_step_down_limit;
u32 flags;
int *corner_map;
u32 num_corners;
int *quot_adjust;
int *mem_acc_corner_map;
int num_adj_cpus;
int online_cpus;
int *adj_cpus;
int **adj_cpus_save_ctl;
int **adj_cpus_save_irq;
int **adj_cpus_last_volt;
int **adj_cpus_quot_adjust;
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)
#define CPR_DEBUG_MASK_API BIT(1)
static int cpr_debug_enable;
#if defined(CONFIG_DEBUG_FS)
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static struct dentry *cpr_debugfs_base;
#endif
static DEFINE_MUTEX(cpr_regulator_list_mutex);
static LIST_HEAD(cpr_regulator_list);
module_param_named(debug_enable, cpr_debug_enable, int, S_IRUGO | S_IWUSR);
#define cpr_debug(cpr_vreg, message, ...) \
do { \
if (cpr_debug_enable & CPR_DEBUG_MASK_API) \
pr_info("%s: " message, (cpr_vreg)->rdesc.name, \
##__VA_ARGS__); \
} while (0)
#define cpr_debug_irq(cpr_vreg, message, ...) \
do { \
if (cpr_debug_enable & CPR_DEBUG_MASK_IRQ) \
pr_info("%s: " message, (cpr_vreg)->rdesc.name, \
##__VA_ARGS__); \
else \
pr_debug("%s: " message, (cpr_vreg)->rdesc.name, \
##__VA_ARGS__); \
} while (0)
#define cpr_info(cpr_vreg, message, ...) \
pr_info("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__)
#define cpr_err(cpr_vreg, message, ...) \
pr_err("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__)
static u64 cpr_read_remapped_efuse_row(struct cpr_regulator *cpr_vreg,
u32 row_num)
{
if (row_num - cpr_vreg->remapped_row_base
>= cpr_vreg->num_remapped_rows) {
cpr_err(cpr_vreg, "invalid row=%u, max remapped row=%u\n",
row_num, cpr_vreg->remapped_row_base
+ cpr_vreg->num_remapped_rows - 1);
return 0;
}
return cpr_vreg->remapped_row[row_num - cpr_vreg->remapped_row_base];
}
static u64 cpr_read_efuse_row(struct cpr_regulator *cpr_vreg, u32 row_num,
bool use_tz_api)
{
int rc;
u64 efuse_bits;
struct scm_desc desc = {0};
struct cpr_read_req {
u32 row_address;
int addr_type;
} req;
struct cpr_read_rsp {
u32 row_data[2];
u32 status;
} rsp;
if (cpr_vreg->remapped_row && row_num >= cpr_vreg->remapped_row_base)
return cpr_read_remapped_efuse_row(cpr_vreg, row_num);
if (!use_tz_api) {
efuse_bits = readq_relaxed(cpr_vreg->efuse_base
+ row_num * BYTES_PER_FUSE_ROW);
return efuse_bits;
}
desc.args[0] = req.row_address = cpr_vreg->efuse_addr +
row_num * BYTES_PER_FUSE_ROW;
desc.args[1] = req.addr_type = 0;
desc.arginfo = SCM_ARGS(2);
efuse_bits = 0;
if (!is_scm_armv8()) {
rc = scm_call(SCM_SVC_FUSE, SCM_FUSE_READ,
&req, sizeof(req), &rsp, sizeof(rsp));
} else {
rc = scm_call2(SCM_SIP_FNID(SCM_SVC_FUSE, SCM_FUSE_READ),
&desc);
rsp.row_data[0] = desc.ret[0];
rsp.row_data[1] = desc.ret[1];
rsp.status = desc.ret[2];
}
if (rc) {
cpr_err(cpr_vreg, "read row %d failed, err code = %d",
row_num, rc);
} else {
efuse_bits = ((u64)(rsp.row_data[1]) << 32) +
(u64)rsp.row_data[0];
}
return efuse_bits;
}
/**
* cpr_read_efuse_param() - read a parameter from one or two eFuse rows
* @cpr_vreg: Pointer to cpr_regulator struct for this regulator.
* @row_start: Fuse row number to start reading from.
* @bit_start: The LSB of the parameter to read from the fuse.
* @bit_len: The length of the parameter in bits.
* @use_tz_api: Flag to indicate if an SCM call should be used to read the fuse.
*
* This function reads a parameter of specified offset and bit size out of one
* or two consecutive eFuse rows. This allows for the reading of parameters
* that happen to be split between two eFuse rows.
*
* Returns the fuse parameter on success or 0 on failure.
*/
static u64 cpr_read_efuse_param(struct cpr_regulator *cpr_vreg, int row_start,
int bit_start, int bit_len, bool use_tz_api)
{
u64 fuse[2];
u64 param = 0;
int bits_first, bits_second;
if (bit_start < 0) {
cpr_err(cpr_vreg, "Invalid LSB = %d specified\n", bit_start);
return 0;
}
if (bit_len < 0 || bit_len > 64) {
cpr_err(cpr_vreg, "Invalid bit length = %d specified\n",
bit_len);
return 0;
}
/* Allow bit indexing to start beyond the end of the start row. */
if (bit_start >= 64) {
row_start += bit_start >> 6; /* equivalent to bit_start / 64 */
bit_start &= 0x3F;
}
fuse[0] = cpr_read_efuse_row(cpr_vreg, row_start, use_tz_api);
if (bit_start == 0 && bit_len == 64) {
param = fuse[0];
} else if (bit_start + bit_len <= 64) {
param = (fuse[0] >> bit_start) & ((1ULL << bit_len) - 1);
} else {
fuse[1] = cpr_read_efuse_row(cpr_vreg, row_start + 1,
use_tz_api);
bits_first = 64 - bit_start;
bits_second = bit_len - bits_first;
param = (fuse[0] >> bit_start) & ((1ULL << bits_first) - 1);
param |= (fuse[1] & ((1ULL << bits_second) - 1)) << bits_first;
}
return param;
}
static bool cpr_is_allowed(struct cpr_regulator *cpr_vreg)
{
if (cpr_vreg->cpr_fuse_disable || !cpr_vreg->enable ||
cpr_vreg->cpr_thermal_disable)
return false;
else
return true;
}
static void cpr_write(struct cpr_regulator *cpr_vreg, u32 offset, u32 value)
{
writel_relaxed(value, cpr_vreg->rbcpr_base + offset);
}
static u32 cpr_read(struct cpr_regulator *cpr_vreg, u32 offset)
{
return readl_relaxed(cpr_vreg->rbcpr_base + offset);
}
static void cpr_masked_write(struct cpr_regulator *cpr_vreg, u32 offset,
u32 mask, u32 value)
{
u32 reg_val;
reg_val = readl_relaxed(cpr_vreg->rbcpr_base + offset);
reg_val &= ~mask;
reg_val |= value & mask;
writel_relaxed(reg_val, cpr_vreg->rbcpr_base + offset);
}
static void cpr_irq_clr(struct cpr_regulator *cpr_vreg)
{
cpr_write(cpr_vreg, REG_RBIF_IRQ_CLEAR, CPR_INT_ALL);
}
static void cpr_irq_clr_nack(struct cpr_regulator *cpr_vreg)
{
cpr_irq_clr(cpr_vreg);
cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
}
static void cpr_irq_clr_ack(struct cpr_regulator *cpr_vreg)
{
cpr_irq_clr(cpr_vreg);
cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1);
}
static void cpr_irq_set(struct cpr_regulator *cpr_vreg, u32 int_bits)
{
cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), int_bits);
}
static void cpr_ctl_modify(struct cpr_regulator *cpr_vreg, u32 mask, u32 value)
{
cpr_masked_write(cpr_vreg, REG_RBCPR_CTL, mask, value);
}
static void cpr_ctl_enable(struct cpr_regulator *cpr_vreg, int corner)
{
u32 val;
if (cpr_vreg->is_cpr_suspended)
return;
/* Program Consecutive Up & Down */
val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK)
<< RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) |
(cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK);
cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST,
RBIF_TIMER_ADJ_CONS_UP_MASK |
RBIF_TIMER_ADJ_CONS_DOWN_MASK, val);
cpr_masked_write(cpr_vreg, REG_RBCPR_CTL,
RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN |
RBCPR_CTL_SW_AUTO_CONT_ACK_EN,
cpr_vreg->save_ctl[corner]);
cpr_irq_set(cpr_vreg, cpr_vreg->save_irq[corner]);
if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled &&
(cpr_vreg->ceiling_volt[corner] >
cpr_vreg->floor_volt[corner]))
val = RBCPR_CTL_LOOP_EN;
else
val = 0;
cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, val);
}
static void cpr_ctl_disable(struct cpr_regulator *cpr_vreg)
{
if (cpr_vreg->is_cpr_suspended)
return;
cpr_irq_set(cpr_vreg, 0);
cpr_ctl_modify(cpr_vreg, RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN |
RBCPR_CTL_SW_AUTO_CONT_ACK_EN, 0);
cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST,
RBIF_TIMER_ADJ_CONS_UP_MASK |
RBIF_TIMER_ADJ_CONS_DOWN_MASK, 0);
cpr_irq_clr(cpr_vreg);
cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1);
cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0);
}
static bool cpr_ctl_is_enabled(struct cpr_regulator *cpr_vreg)
{
u32 reg_val;
reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL);
return reg_val & RBCPR_CTL_LOOP_EN;
}
static bool cpr_ctl_is_busy(struct cpr_regulator *cpr_vreg)
{
u32 reg_val;
reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
return reg_val & RBCPR_RESULT0_BUSY_MASK;
}
static void cpr_corner_save(struct cpr_regulator *cpr_vreg, int corner)
{
cpr_vreg->save_ctl[corner] = cpr_read(cpr_vreg, REG_RBCPR_CTL);
cpr_vreg->save_irq[corner] =
cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line));
}
static void cpr_corner_restore(struct cpr_regulator *cpr_vreg, int corner)
{
u32 gcnt, ctl, irq, ro_sel, step_quot;
int fuse_corner = cpr_vreg->corner_map[corner];
int i;
ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner];
gcnt = cpr_vreg->gcnt | (cpr_vreg->cpr_fuse_target_quot[fuse_corner] -
cpr_vreg->quot_adjust[corner]);
/* Program the step quotient and idle clocks */
step_quot = ((cpr_vreg->idle_clocks & RBCPR_STEP_QUOT_IDLE_CLK_MASK)
<< RBCPR_STEP_QUOT_IDLE_CLK_SHIFT) |
(cpr_vreg->step_quotient[fuse_corner]
& RBCPR_STEP_QUOT_STEPQUOT_MASK);
cpr_write(cpr_vreg, REG_RBCPR_STEP_QUOT, step_quot);
/* 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);
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel), gcnt);
ctl = cpr_vreg->save_ctl[corner];
cpr_write(cpr_vreg, REG_RBCPR_CTL, ctl);
irq = cpr_vreg->save_irq[corner];
cpr_irq_set(cpr_vreg, irq);
cpr_debug(cpr_vreg, "gcnt = 0x%08x, ctl = 0x%08x, irq = 0x%08x\n",
gcnt, ctl, irq);
}
static void cpr_corner_switch(struct cpr_regulator *cpr_vreg, int corner)
{
if (cpr_vreg->corner == corner)
return;
cpr_corner_restore(cpr_vreg, corner);
}
static int cpr_apc_set(struct cpr_regulator *cpr_vreg, u32 new_volt)
{
int max_volt, rc;
max_volt = cpr_vreg->ceiling_max;
rc = regulator_set_voltage(cpr_vreg->vdd_apc, new_volt, max_volt);
if (rc)
cpr_err(cpr_vreg, "set: vdd_apc = %d uV: rc=%d\n",
new_volt, rc);
return rc;
}
static int cpr_mx_get(struct cpr_regulator *cpr_vreg, int corner, int apc_volt)
{
int vdd_mx;
int fuse_corner = cpr_vreg->corner_map[corner];
int highest_fuse_corner = cpr_vreg->num_fuse_corners;
switch (cpr_vreg->vdd_mx_vmin_method) {
case VDD_MX_VMIN_APC:
vdd_mx = apc_volt;
break;
case VDD_MX_VMIN_APC_CORNER_CEILING:
vdd_mx = cpr_vreg->fuse_ceiling_volt[fuse_corner];
break;
case VDD_MX_VMIN_APC_SLOW_CORNER_CEILING:
vdd_mx = cpr_vreg->fuse_ceiling_volt[highest_fuse_corner];
break;
case VDD_MX_VMIN_MX_VMAX:
vdd_mx = cpr_vreg->vdd_mx_vmax;
break;
case VDD_MX_VMIN_APC_FUSE_CORNER_MAP:
vdd_mx = cpr_vreg->vdd_mx_corner_map[fuse_corner];
break;
case VDD_MX_VMIN_APC_CORNER_MAP:
vdd_mx = cpr_vreg->vdd_mx_corner_map[corner];
break;
default:
vdd_mx = 0;
break;
}
return vdd_mx;
}
static int cpr_mx_set(struct cpr_regulator *cpr_vreg, int corner,
int vdd_mx_vmin)
{
int rc;
int fuse_corner = cpr_vreg->corner_map[corner];
rc = regulator_set_voltage(cpr_vreg->vdd_mx, vdd_mx_vmin,
cpr_vreg->vdd_mx_vmax);
cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] %d uV\n", corner,
fuse_corner, vdd_mx_vmin);
if (!rc) {
cpr_vreg->vdd_mx_vmin = vdd_mx_vmin;
} else {
cpr_err(cpr_vreg, "set: vdd_mx [corner:%d, fuse_corner:%d] = %d uV failed: rc=%d\n",
corner, fuse_corner, vdd_mx_vmin, rc);
}
return rc;
}
static int cpr_scale_voltage(struct cpr_regulator *cpr_vreg, int corner,
int new_apc_volt, enum voltage_change_dir dir)
{
int rc = 0, vdd_mx_vmin = 0;
int mem_acc_corner = cpr_vreg->mem_acc_corner_map[corner];
int fuse_corner = cpr_vreg->corner_map[corner];
int apc_corner, vsens_corner;
/* Determine the vdd_mx voltage */
if (dir != NO_CHANGE && cpr_vreg->vdd_mx != NULL)
vdd_mx_vmin = cpr_mx_get(cpr_vreg, corner, new_apc_volt);
if (cpr_vreg->vdd_vsens_voltage && cpr_vreg->vsens_enabled) {
rc = regulator_disable(cpr_vreg->vdd_vsens_voltage);
if (!rc)
cpr_vreg->vsens_enabled = false;
}
if (dir == DOWN) {
if (!rc && cpr_vreg->mem_acc_vreg)
rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg,
mem_acc_corner, mem_acc_corner);
if (!rc && cpr_vreg->rpm_apc_vreg) {
apc_corner = cpr_vreg->rpm_apc_corner_map[corner];
rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg,
apc_corner, apc_corner);
if (rc)
cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n",
rc);
}
}
if (!rc && vdd_mx_vmin && dir == UP) {
if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin)
rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin);
}
if (!rc)
rc = cpr_apc_set(cpr_vreg, new_apc_volt);
if (dir == UP) {
if (!rc && cpr_vreg->mem_acc_vreg)
rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg,
mem_acc_corner, mem_acc_corner);
if (!rc && cpr_vreg->rpm_apc_vreg) {
apc_corner = cpr_vreg->rpm_apc_corner_map[corner];
rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg,
apc_corner, apc_corner);
if (rc)
cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n",
rc);
}
}
if (!rc && vdd_mx_vmin && dir == DOWN) {
if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin)
rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin);
}
if (!rc && cpr_vreg->vdd_vsens_corner) {
vsens_corner = cpr_vreg->vsens_corner_map[fuse_corner];
rc = regulator_set_voltage(cpr_vreg->vdd_vsens_corner,
vsens_corner, vsens_corner);
}
if (!rc && cpr_vreg->vdd_vsens_voltage) {
rc = regulator_set_voltage(cpr_vreg->vdd_vsens_voltage,
cpr_vreg->floor_volt[corner],
cpr_vreg->ceiling_volt[corner]);
if (!rc && !cpr_vreg->vsens_enabled) {
rc = regulator_enable(cpr_vreg->vdd_vsens_voltage);
if (!rc)
cpr_vreg->vsens_enabled = true;
}
}
return rc;
}
static void cpr_scale(struct cpr_regulator *cpr_vreg,
enum voltage_change_dir dir)
{
u32 reg_val, error_steps, reg_mask;
int last_volt, new_volt, corner, fuse_corner;
u32 gcnt, quot;
corner = cpr_vreg->corner;
fuse_corner = cpr_vreg->corner_map[corner];
reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
error_steps = (reg_val >> RBCPR_RESULT0_ERROR_STEPS_SHIFT)
& RBCPR_RESULT0_ERROR_STEPS_MASK;
last_volt = cpr_vreg->last_volt[corner];
cpr_debug_irq(cpr_vreg,
"last_volt[corner:%d, fuse_corner:%d] = %d uV\n",
corner, fuse_corner, last_volt);
gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET
(cpr_vreg->cpr_fuse_ro_sel[fuse_corner]));
quot = gcnt & ((1 << RBCPR_GCNT_TARGET_GCNT_SHIFT) - 1);
if (dir == UP) {
if (cpr_vreg->clamp_timer_interval
&& error_steps < cpr_vreg->up_threshold) {
/*
* Handle the case where another measurement started
* after the interrupt was triggered due to a core
* exiting from power collapse.
*/
error_steps = max(cpr_vreg->up_threshold,
cpr_vreg->vdd_apc_step_up_limit);
}
cpr_debug_irq(cpr_vreg,
"Up: cpr status = 0x%08x (error_steps=%d)\n",
reg_val, error_steps);
if (last_volt >= cpr_vreg->ceiling_volt[corner]) {
cpr_debug_irq(cpr_vreg,
"[corn:%d, fuse_corn:%d] @ ceiling: %d >= %d: NACK\n",
corner, fuse_corner, last_volt,
cpr_vreg->ceiling_volt[corner]);
cpr_irq_clr_nack(cpr_vreg);
cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n",
gcnt, quot);
/* Maximize the UP threshold */
reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK <<
RBCPR_CTL_UP_THRESHOLD_SHIFT;
reg_val = reg_mask;
cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
/* Disable UP interrupt */
cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_UP);
return;
}
if (error_steps > cpr_vreg->vdd_apc_step_up_limit) {
cpr_debug_irq(cpr_vreg,
"%d is over up-limit(%d): Clamp\n",
error_steps,
cpr_vreg->vdd_apc_step_up_limit);
error_steps = cpr_vreg->vdd_apc_step_up_limit;
}
/* Calculate new voltage */
new_volt = last_volt + (error_steps * cpr_vreg->step_volt);
if (new_volt > cpr_vreg->ceiling_volt[corner]) {
cpr_debug_irq(cpr_vreg,
"new_volt(%d) >= ceiling(%d): Clamp\n",
new_volt,
cpr_vreg->ceiling_volt[corner]);
new_volt = cpr_vreg->ceiling_volt[corner];
}
if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) {
cpr_irq_clr_nack(cpr_vreg);
return;
}
cpr_vreg->last_volt[corner] = new_volt;
/* Disable auto nack down */
reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
reg_val = 0;
cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
/* Re-enable default interrupts */
cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
/* Ack */
cpr_irq_clr_ack(cpr_vreg);
cpr_debug_irq(cpr_vreg,
"UP: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n",
corner, fuse_corner, new_volt);
} else if (dir == DOWN) {
if (cpr_vreg->clamp_timer_interval
&& error_steps < cpr_vreg->down_threshold) {
/*
* Handle the case where another measurement started
* after the interrupt was triggered due to a core
* exiting from power collapse.
*/
error_steps = max(cpr_vreg->down_threshold,
cpr_vreg->vdd_apc_step_down_limit);
}
cpr_debug_irq(cpr_vreg,
"Down: cpr status = 0x%08x (error_steps=%d)\n",
reg_val, error_steps);
if (last_volt <= cpr_vreg->floor_volt[corner]) {
cpr_debug_irq(cpr_vreg,
"[corn:%d, fuse_corner:%d] @ floor: %d <= %d: NACK\n",
corner, fuse_corner, last_volt,
cpr_vreg->floor_volt[corner]);
cpr_irq_clr_nack(cpr_vreg);
cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n",
gcnt, quot);
/* Enable auto nack down */
reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
reg_val = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
/* Disable DOWN interrupt */
cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_DOWN);
return;
}
if (error_steps > cpr_vreg->vdd_apc_step_down_limit) {
cpr_debug_irq(cpr_vreg,
"%d is over down-limit(%d): Clamp\n",
error_steps,
cpr_vreg->vdd_apc_step_down_limit);
error_steps = cpr_vreg->vdd_apc_step_down_limit;
}
/* Calculte new voltage */
new_volt = last_volt - (error_steps * cpr_vreg->step_volt);
if (new_volt < cpr_vreg->floor_volt[corner]) {
cpr_debug_irq(cpr_vreg,
"new_volt(%d) < floor(%d): Clamp\n",
new_volt,
cpr_vreg->floor_volt[corner]);
new_volt = cpr_vreg->floor_volt[corner];
}
if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) {
cpr_irq_clr_nack(cpr_vreg);
return;
}
cpr_vreg->last_volt[corner] = new_volt;
/* Restore default threshold for UP */
reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK <<
RBCPR_CTL_UP_THRESHOLD_SHIFT;
reg_val = cpr_vreg->up_threshold <<
RBCPR_CTL_UP_THRESHOLD_SHIFT;
cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
/* Re-enable default interrupts */
cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
/* Ack */
cpr_irq_clr_ack(cpr_vreg);
cpr_debug_irq(cpr_vreg,
"DOWN: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n",
corner, fuse_corner, new_volt);
}
}
static irqreturn_t cpr_irq_handler(int irq, void *dev)
{
struct cpr_regulator *cpr_vreg = dev;
u32 reg_val;
mutex_lock(&cpr_vreg->cpr_mutex);
reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
if (cpr_vreg->flags & FLAGS_IGNORE_1ST_IRQ_STATUS)
reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
cpr_debug_irq(cpr_vreg, "IRQ_STATUS = 0x%02X\n", reg_val);
if (!cpr_ctl_is_enabled(cpr_vreg)) {
cpr_debug_irq(cpr_vreg, "CPR is disabled\n");
goto _exit;
} else if (cpr_ctl_is_busy(cpr_vreg)
&& !cpr_vreg->clamp_timer_interval) {
cpr_debug_irq(cpr_vreg, "CPR measurement is not ready\n");
goto _exit;
} else if (!cpr_is_allowed(cpr_vreg)) {
reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL);
cpr_err(cpr_vreg, "Interrupt broken? RBCPR_CTL = 0x%02X\n",
reg_val);
goto _exit;
}
/* Following sequence of handling is as per each IRQ's priority */
if (reg_val & CPR_INT_UP) {
cpr_scale(cpr_vreg, UP);
} else if (reg_val & CPR_INT_DOWN) {
cpr_scale(cpr_vreg, DOWN);
} else if (reg_val & CPR_INT_MIN) {
cpr_irq_clr_nack(cpr_vreg);
} else if (reg_val & CPR_INT_MAX) {
cpr_irq_clr_nack(cpr_vreg);
} else if (reg_val & CPR_INT_MID) {
/* RBCPR_CTL_SW_AUTO_CONT_ACK_EN is enabled */
cpr_debug_irq(cpr_vreg, "IRQ occurred for Mid Flag\n");
} else {
cpr_debug_irq(cpr_vreg,
"IRQ occurred for unknown flag (0x%08x)\n", reg_val);
}
/* Save register values for the corner */
cpr_corner_save(cpr_vreg, cpr_vreg->corner);
_exit:
mutex_unlock(&cpr_vreg->cpr_mutex);
return IRQ_HANDLED;
}
/**
* cmp_int() - int comparison function to be passed into the sort() function
* which leads to ascending sorting
* @a: First int value
* @b: Second int value
*
* Return: >0 if a > b, 0 if a == b, <0 if a < b
*/
static int cmp_int(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
struct cpr_aging_sensor_info *aging_sensor_info)
{
int quot_min, quot_max, is_aging_measurement, aging_measurement_count;
int quot_min_scaled, quot_max_scaled, quot_delta_scaled_sum;
int retries, rc = 0, sel_fast = 0, i, quot_delta_scaled;
u32 val, gcnt_ref, gcnt;
int *quot_delta_results, filtered_count;
quot_delta_results = kcalloc(CPR_AGING_MEASUREMENT_ITERATIONS,
sizeof(*quot_delta_results), GFP_ATOMIC);
if (!quot_delta_results)
return -ENOMEM;
/* 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 */
gcnt_ref = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000;
gcnt = gcnt_ref * 3 / 2;
val = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) <<
RBCPR_GCNT_TARGET_GCNT_SHIFT;
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), val);
cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), val);
val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(0));
cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET0 = 0x%08x\n", val);
val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(1));
cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET1 = 0x%08x\n", val);
/* Program TIMER_INTERVAL to zero */
cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, 0);
/* Bypass sensors in collapsible domain */
if (cpr_vreg->aging_info->aging_sensor_bypass)
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 */
cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, RBCPR_HTOL_AGE_PAGE);
aging_measurement_count = 0;
quot_delta_scaled_sum = 0;
for (i = 0; i < CPR_AGING_MEASUREMENT_ITERATIONS; i++) {
/* Send cont nack */
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;
}
/* Check for PAGE_IS_AGE flag in status register */
val = cpr_read(cpr_vreg, REG_RBCPR_HTOL_AGE);
is_aging_measurement = val & RBCPR_AGE_DATA_STATUS;
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) {
quot_min = RBCPR_DEBUG1_QUOT_FAST(val);
quot_max = RBCPR_DEBUG1_QUOT_SLOW(val);
} else {
quot_min = RBCPR_DEBUG1_QUOT_SLOW(val);
quot_max = RBCPR_DEBUG1_QUOT_FAST(val);
}
/*
* Scale the quotients so that they are equivalent to the fused
* values. This accounts for the difference in measurement
* interval times.
*/
quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1);
quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1);
quot_delta_scaled = 0;
if (is_aging_measurement) {
quot_delta_scaled = quot_min_scaled - quot_max_scaled;
quot_delta_results[aging_measurement_count++] =
quot_delta_scaled;
}
cpr_debug(cpr_vreg,
"Age sensor[%d]: measurement[%d]: page_is_age=%u quot_min = %d, quot_max = %d quot_min_scaled = %d, quot_max_scaled = %d quot_delta_scaled = %d\n",
aging_sensor_info->sensor_id, i, is_aging_measurement,
quot_min, quot_max, quot_min_scaled, quot_max_scaled,
quot_delta_scaled);
}
filtered_count
= aging_measurement_count - CPR_AGING_MEASUREMENT_FILTER * 2;
if (filtered_count > 0) {
sort(quot_delta_results, aging_measurement_count,
sizeof(*quot_delta_results), cmp_int, NULL);
quot_delta_scaled_sum = 0;
for (i = 0; i < filtered_count; i++)
quot_delta_scaled_sum
+= quot_delta_results[i
+ CPR_AGING_MEASUREMENT_FILTER];
aging_sensor_info->current_quot_diff
= quot_delta_scaled_sum / filtered_count;
cpr_debug(cpr_vreg,
"Age sensor[%d]: average aging quotient delta = %d (count = %d)\n",
aging_sensor_info->sensor_id,
aging_sensor_info->current_quot_diff, filtered_count);
} else {
cpr_err(cpr_vreg, "%d aging measurements completed after %d iterations\n",
aging_measurement_count,
CPR_AGING_MEASUREMENT_ITERATIONS);
rc = -EBUSY;
}
_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 */
if (cpr_vreg->aging_info->aging_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);
return cpr_vreg->vreg_enabled;
}
static int cpr_regulator_enable(struct regulator_dev *rdev)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
int rc = 0;
/* Enable dependency power before vdd_apc */
if (cpr_vreg->vdd_mx) {
rc = regulator_enable(cpr_vreg->vdd_mx);
if (rc) {
cpr_err(cpr_vreg, "regulator_enable: vdd_mx: rc=%d\n",
rc);
return rc;
}
}
rc = regulator_enable(cpr_vreg->vdd_apc);
if (rc) {
cpr_err(cpr_vreg, "regulator_enable: vdd_apc: rc=%d\n", rc);
return rc;
}
mutex_lock(&cpr_vreg->cpr_mutex);
cpr_vreg->vreg_enabled = true;
if (cpr_is_allowed(cpr_vreg) && cpr_vreg->corner) {
cpr_irq_clr(cpr_vreg);
cpr_corner_restore(cpr_vreg, cpr_vreg->corner);
cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
}
mutex_unlock(&cpr_vreg->cpr_mutex);
return rc;
}
static int cpr_regulator_disable(struct regulator_dev *rdev)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
int rc;
rc = regulator_disable(cpr_vreg->vdd_apc);
if (!rc) {
if (cpr_vreg->vdd_mx)
rc = regulator_disable(cpr_vreg->vdd_mx);
if (rc) {
cpr_err(cpr_vreg, "regulator_disable: vdd_mx: rc=%d\n",
rc);
return rc;
}
mutex_lock(&cpr_vreg->cpr_mutex);
cpr_vreg->vreg_enabled = false;
if (cpr_is_allowed(cpr_vreg))
cpr_ctl_disable(cpr_vreg);
mutex_unlock(&cpr_vreg->cpr_mutex);
} else {
cpr_err(cpr_vreg, "regulator_disable: vdd_apc: rc=%d\n", rc);
}
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;
}
/* Force PWM mode */
rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_NORMAL);
if (rc) {
cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
REGULATOR_MODE_NORMAL, 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();
/* Set to initial mode */
rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_IDLE);
if (rc) {
cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
REGULATOR_MODE_IDLE, rc);
return rc;
}
/* 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;
}
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
/* 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;
int fuse_corner = cpr_vreg->corner_map[corner];
if (cpr_is_allowed(cpr_vreg)) {
cpr_ctl_disable(cpr_vreg);
new_volt = cpr_vreg->last_volt[corner];
} else {
new_volt = cpr_vreg->open_loop_volt[corner];
}
cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] = %d uV\n",
corner, fuse_corner, new_volt);
if (corner > cpr_vreg->corner)
change_dir = UP;
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)
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
return rc;
if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled) {
cpr_irq_clr(cpr_vreg);
if (reset_quot)
cpr_corner_restore(cpr_vreg, corner);
else
cpr_corner_switch(cpr_vreg, corner);
cpr_ctl_enable(cpr_vreg, corner);
}
cpr_vreg->corner = corner;
return rc;
}
static int cpr_regulator_set_voltage_op(struct regulator_dev *rdev,
int corner, int corner_max, unsigned *selector)
{
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
int rc;
mutex_lock(&cpr_vreg->cpr_mutex);
rc = cpr_regulator_set_voltage(rdev, corner, false);
mutex_unlock(&cpr_vreg->cpr_mutex);
return rc;
}
static int cpr_regulator_get_voltage(struct regulator_dev *rdev)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
return cpr_vreg->corner;
}
/**
* cpr_regulator_list_corner_voltage() - return the ceiling voltage mapped to
* the specified voltage corner
* @rdev: Regulator device pointer for the cpr-regulator
* @corner: Voltage corner
*
* This function is passed as a callback function into the regulator ops that
* are registered for each cpr-regulator device.
*
* Return: voltage value in microvolts or -EINVAL if the corner is out of range
*/
static int cpr_regulator_list_corner_voltage(struct regulator_dev *rdev,
int corner)
{
struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
if (corner >= CPR_CORNER_MIN && corner <= cpr_vreg->num_corners)
return cpr_vreg->ceiling_volt[corner];
else
return -EINVAL;
}
static struct regulator_ops cpr_corner_ops = {
.enable = cpr_regulator_enable,
.disable = cpr_regulator_disable,
.is_enabled = cpr_regulator_is_enabled,
.set_voltage = cpr_regulator_set_voltage_op,
.get_voltage = cpr_regulator_get_voltage,
.list_corner_voltage = cpr_regulator_list_corner_voltage,
};
#ifdef CONFIG_PM
static int cpr_suspend(struct cpr_regulator *cpr_vreg)
{
cpr_debug(cpr_vreg, "suspend\n");
cpr_ctl_disable(cpr_vreg);
cpr_irq_clr(cpr_vreg);
return 0;
}
static int cpr_resume(struct cpr_regulator *cpr_vreg)
{
cpr_debug(cpr_vreg, "resume\n");
cpr_irq_clr(cpr_vreg);
cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
return 0;
}
static int cpr_regulator_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev);
int rc = 0;
mutex_lock(&cpr_vreg->cpr_mutex);
if (cpr_is_allowed(cpr_vreg))
rc = cpr_suspend(cpr_vreg);
cpr_vreg->is_cpr_suspended = true;
mutex_unlock(&cpr_vreg->cpr_mutex);
return rc;
}
static int cpr_regulator_resume(struct platform_device *pdev)
{
struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev);
int rc = 0;
mutex_lock(&cpr_vreg->cpr_mutex);
cpr_vreg->is_cpr_suspended = false;
if (cpr_is_allowed(cpr_vreg))
rc = cpr_resume(cpr_vreg);
mutex_unlock(&cpr_vreg->cpr_mutex);
return rc;
}
#else
#define cpr_regulator_suspend NULL
#define cpr_regulator_resume NULL
#endif
static int cpr_config(struct cpr_regulator *cpr_vreg, struct device *dev)
{
int i;
u32 val, gcnt, reg;
void __iomem *rbcpr_clk;
int size;
if (cpr_vreg->rbcpr_clk_addr) {
/* Use 19.2 MHz clock for CPR. */
rbcpr_clk = ioremap(cpr_vreg->rbcpr_clk_addr, 4);
if (!rbcpr_clk) {
cpr_err(cpr_vreg, "Unable to map rbcpr_clk\n");
return -EINVAL;
}
reg = readl_relaxed(rbcpr_clk);
reg &= ~RBCPR_CLK_SEL_MASK;
reg |= RBCPR_CLK_SEL_19P2_MHZ & RBCPR_CLK_SEL_MASK;
writel_relaxed(reg, rbcpr_clk);
iounmap(rbcpr_clk);
}
/* Disable interrupt and CPR */
cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), 0);
cpr_write(cpr_vreg, REG_RBCPR_CTL, 0);
/* Program the default HW Ceiling, Floor and vlevel */
val = ((RBIF_LIMIT_CEILING_DEFAULT & RBIF_LIMIT_CEILING_MASK)
<< RBIF_LIMIT_CEILING_SHIFT)
| (RBIF_LIMIT_FLOOR_DEFAULT & RBIF_LIMIT_FLOOR_MASK);
cpr_write(cpr_vreg, REG_RBIF_LIMIT, val);
cpr_write(cpr_vreg, REG_RBIF_SW_VLEVEL, RBIF_SW_VLEVEL_DEFAULT);
/* 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);
/* Init and save gcnt */
gcnt = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000;
gcnt = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) <<
RBCPR_GCNT_TARGET_GCNT_SHIFT;
cpr_vreg->gcnt = gcnt;
/* 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);
cpr_info(cpr_vreg, "Timer count: 0x%0x (for %d us)\n", val,
cpr_vreg->timer_delay_us);
/* Program Consecutive Up & Down */
val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK)
<< RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) |
(cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK) |
((cpr_vreg->clamp_timer_interval & RBIF_TIMER_ADJ_CLAMP_INT_MASK)
<< RBIF_TIMER_ADJ_CLAMP_INT_SHIFT);
cpr_write(cpr_vreg, REG_RBIF_TIMER_ADJUST, val);
/* Program the control register */
cpr_vreg->up_threshold &= RBCPR_CTL_UP_THRESHOLD_MASK;
cpr_vreg->down_threshold &= RBCPR_CTL_DN_THRESHOLD_MASK;
val = (cpr_vreg->up_threshold << RBCPR_CTL_UP_THRESHOLD_SHIFT)
| (cpr_vreg->down_threshold << RBCPR_CTL_DN_THRESHOLD_SHIFT);
val |= RBCPR_CTL_TIMER_EN | RBCPR_CTL_COUNT_MODE;
val |= RBCPR_CTL_SW_AUTO_CONT_ACK_EN;
cpr_write(cpr_vreg, REG_RBCPR_CTL, val);
cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
val = cpr_read(cpr_vreg, REG_RBCPR_VERSION);
if (val <= RBCPR_VER_2)
cpr_vreg->flags |= FLAGS_IGNORE_1ST_IRQ_STATUS;
size = cpr_vreg->num_corners + 1;
cpr_vreg->save_ctl = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
cpr_vreg->save_irq = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
if (!cpr_vreg->save_ctl || !cpr_vreg->save_irq)
return -ENOMEM;
for (i = 1; i < size; i++)
cpr_corner_save(cpr_vreg, i);
return 0;
}
static int cpr_fuse_is_setting_expected(struct cpr_regulator *cpr_vreg,
u32 sel_array[5])
{
u64 fuse_bits;
u32 ret;
fuse_bits = cpr_read_efuse_row(cpr_vreg, sel_array[0], sel_array[4]);
ret = (fuse_bits >> sel_array[1]) & ((1 << sel_array[2]) - 1);
if (ret == sel_array[3])
ret = 1;
else
ret = 0;
cpr_info(cpr_vreg, "[row:%d] = 0x%llx @%d:%d == %d ?: %s\n",
sel_array[0], fuse_bits,
sel_array[1], sel_array[2],
sel_array[3],
(ret == 1) ? "yes" : "no");
return ret;
}
static int cpr_voltage_uplift_wa_inc_volt(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
u32 uplift_voltage;
u32 uplift_max_volt = 0;
int highest_fuse_corner = cpr_vreg->num_fuse_corners;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
int rc;
rc = of_property_read_u32(of_node,
"qcom,cpr-uplift-voltage", &uplift_voltage);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr-uplift-voltage is missing, rc = %d", rc);
return rc;
}
rc = of_property_read_u32(of_node,
"qcom,cpr-uplift-max-volt", &uplift_max_volt);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr-uplift-max-volt is missing, rc = %d",
rc);
return rc;
}
cpr_vreg->pvs_corner_v[highest_fuse_corner] += uplift_voltage;
if (cpr_vreg->pvs_corner_v[highest_fuse_corner] > uplift_max_volt)
cpr_vreg->pvs_corner_v[highest_fuse_corner] = uplift_max_volt;
return rc;
}
static int cpr_adjust_init_voltages(struct device_node *of_node,
struct cpr_regulator *cpr_vreg)
{
int tuple_count, tuple_match, i;
u32 index;
u32 volt_adjust = 0;
int len = 0;
int rc = 0;
if (!of_find_property(of_node, "qcom,cpr-init-voltage-adjustment",
&len)) {
/* No initial voltage adjustment needed. */
return 0;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/*
* No matching index to use for initial voltage
* adjustment.
*/
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 (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "qcom,cpr-init-voltage-adjustment length=%d is invalid\n",
len);
return -EINVAL;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
index = tuple_match * cpr_vreg->num_fuse_corners
+ i - CPR_FUSE_CORNER_MIN;
rc = of_property_read_u32_index(of_node,
"qcom,cpr-init-voltage-adjustment", index,
&volt_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-init-voltage-adjustment index %u, rc=%d\n",
index, rc);
return rc;
}
if (volt_adjust) {
cpr_vreg->pvs_corner_v[i] += volt_adjust;
cpr_info(cpr_vreg, "adjusted initial voltage[%d]: %d -> %d uV\n",
i, cpr_vreg->pvs_corner_v[i] - volt_adjust,
cpr_vreg->pvs_corner_v[i]);
}
}
return rc;
}
/*
* Property qcom,cpr-fuse-init-voltage specifies the fuse position of the
* initial voltage for each fuse corner. MSB of the fuse value is a sign
* bit, and the remaining bits define the steps of the offset. Each step has
* units of microvolts defined in the qcom,cpr-fuse-init-voltage-step property.
* The initial voltages can be calculated using the formula:
* pvs_corner_v[corner] = ceiling_volt[corner] + (sign * steps * step_size_uv)
*/
static int cpr_pvs_per_corner_init(struct device_node *of_node,
struct cpr_regulator *cpr_vreg)
{
u64 efuse_bits;
int i, size, sign, steps, step_size_uv, rc;
u32 *fuse_sel, *tmp, *ref_uv;
struct property *prop;
char *init_volt_str;
init_volt_str = cpr_vreg->cpr_fuse_redundant
? "qcom,cpr-fuse-redun-init-voltage"
: "qcom,cpr-fuse-init-voltage";
prop = of_find_property(of_node, init_volt_str, NULL);
if (!prop) {
cpr_err(cpr_vreg, "%s is missing\n", init_volt_str);
return -EINVAL;
}
size = prop->length / sizeof(u32);
if (size != cpr_vreg->num_fuse_corners * 4) {
cpr_err(cpr_vreg,
"fuse position for init voltages is invalid\n");
return -EINVAL;
}
fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL);
if (!fuse_sel) {
cpr_err(cpr_vreg, "memory alloc failed.\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, init_volt_str,
fuse_sel, size);
if (rc < 0) {
cpr_err(cpr_vreg,
"read cpr-fuse-init-voltage failed, rc = %d\n", rc);
kfree(fuse_sel);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,cpr-init-voltage-step",
&step_size_uv);
if (rc < 0) {
cpr_err(cpr_vreg,
"read cpr-init-voltage-step failed, rc = %d\n", rc);
kfree(fuse_sel);
return rc;
}
ref_uv = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*ref_uv),
GFP_KERNEL);
if (!ref_uv) {
cpr_err(cpr_vreg,
"Could not allocate memory for reference voltages\n");
kfree(fuse_sel);
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,cpr-init-voltage-ref",
&ref_uv[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg,
"read qcom,cpr-init-voltage-ref failed, rc = %d\n", rc);
kfree(fuse_sel);
kfree(ref_uv);
return rc;
}
tmp = fuse_sel;
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
efuse_bits = cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
fuse_sel[1], fuse_sel[2], fuse_sel[3]);
sign = (efuse_bits & (1 << (fuse_sel[2] - 1))) ? -1 : 1;
steps = efuse_bits & ((1 << (fuse_sel[2] - 1)) - 1);
cpr_vreg->pvs_corner_v[i] =
ref_uv[i] + sign * steps * step_size_uv;
cpr_vreg->pvs_corner_v[i] = DIV_ROUND_UP(
cpr_vreg->pvs_corner_v[i],
cpr_vreg->step_volt) *
cpr_vreg->step_volt;
cpr_debug(cpr_vreg, "corner %d: sign = %d, steps = %d, volt = %d uV\n",
i, sign, steps, cpr_vreg->pvs_corner_v[i]);
fuse_sel += 4;
}
rc = cpr_adjust_init_voltages(of_node, cpr_vreg);
if (rc)
goto done;
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
if (cpr_vreg->pvs_corner_v[i]
> cpr_vreg->fuse_ceiling_volt[i]) {
cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d above ceiling %d\n",
i, cpr_vreg->pvs_corner_v[i],
cpr_vreg->fuse_ceiling_volt[i]);
cpr_vreg->pvs_corner_v[i]
= cpr_vreg->fuse_ceiling_volt[i];
} else if (cpr_vreg->pvs_corner_v[i] <
cpr_vreg->fuse_floor_volt[i]) {
cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d below floor %d\n",
i, cpr_vreg->pvs_corner_v[i],
cpr_vreg->fuse_floor_volt[i]);
cpr_vreg->pvs_corner_v[i]
= cpr_vreg->fuse_floor_volt[i];
}
}
done:
kfree(tmp);
kfree(ref_uv);
return rc;
}
/*
* A single PVS bin is stored in a fuse that's position is defined either
* in the qcom,pvs-fuse-redun property or in the qcom,pvs-fuse property.
* The fuse value defined in the qcom,pvs-fuse-redun-sel property is used
* to pick between the primary or redudant PVS fuse position.
* After the PVS bin value is read out successfully, it is used as the row
* index to get initial voltages for each fuse corner from the voltage table
* defined in the qcom,pvs-voltage-table property.
*/
static int cpr_pvs_single_bin_init(struct device_node *of_node,
struct cpr_regulator *cpr_vreg)
{
u64 efuse_bits;
u32 pvs_fuse[4], pvs_fuse_redun_sel[5];
int rc, i, stripe_size;
bool redundant;
size_t pvs_bins;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
u32 *tmp;
rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun-sel",
pvs_fuse_redun_sel, 5);
if (rc < 0) {
cpr_err(cpr_vreg, "pvs-fuse-redun-sel missing: rc=%d\n", rc);
return rc;
}
redundant = cpr_fuse_is_setting_expected(cpr_vreg, pvs_fuse_redun_sel);
if (redundant) {
rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun",
pvs_fuse, 4);
if (rc < 0) {
cpr_err(cpr_vreg, "pvs-fuse-redun missing: rc=%d\n",
rc);
return rc;
}
} else {
rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse",
pvs_fuse, 4);
if (rc < 0) {
cpr_err(cpr_vreg, "pvs-fuse missing: rc=%d\n", rc);
return rc;
}
}
/* Construct PVS process # from the efuse bits */
efuse_bits = cpr_read_efuse_row(cpr_vreg, pvs_fuse[0], pvs_fuse[3]);
cpr_vreg->pvs_bin = (efuse_bits >> pvs_fuse[1]) &
((1 << pvs_fuse[2]) - 1);
pvs_bins = 1 << pvs_fuse[2];
stripe_size = cpr_vreg->num_fuse_corners;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
tmp = kzalloc(sizeof(u32) * pvs_bins * stripe_size, GFP_KERNEL);
if (!tmp) {
cpr_err(cpr_vreg, "memory alloc failed\n");
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,pvs-voltage-table",
tmp, pvs_bins * stripe_size);
if (rc < 0) {
cpr_err(cpr_vreg, "pvs-voltage-table missing: rc=%d\n", rc);
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
kfree(tmp);
return rc;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
cpr_vreg->pvs_corner_v[i] = tmp[cpr_vreg->pvs_bin *
stripe_size + i - 1];
kfree(tmp);
rc = cpr_adjust_init_voltages(of_node, cpr_vreg);
if (rc)
return rc;
return 0;
}
/*
* The function reads VDD_MX dependency parameters from device node.
* Select the qcom,vdd-mx-corner-map length equal to either num_fuse_corners
* or num_corners based on selected vdd-mx-vmin-method.
*/
static int cpr_parse_vdd_mx_parameters(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
u32 corner_map_len;
int rc, len, size;
rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmax",
&cpr_vreg->vdd_mx_vmax);
if (rc < 0) {
cpr_err(cpr_vreg, "vdd-mx-vmax missing: rc=%d\n", rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmin-method",
&cpr_vreg->vdd_mx_vmin_method);
if (rc < 0) {
cpr_err(cpr_vreg, "vdd-mx-vmin-method missing: rc=%d\n",
rc);
return rc;
}
if (cpr_vreg->vdd_mx_vmin_method > VDD_MX_VMIN_APC_CORNER_MAP) {
cpr_err(cpr_vreg, "Invalid vdd-mx-vmin-method(%d)\n",
cpr_vreg->vdd_mx_vmin_method);
return -EINVAL;
}
switch (cpr_vreg->vdd_mx_vmin_method) {
case VDD_MX_VMIN_APC_FUSE_CORNER_MAP:
corner_map_len = cpr_vreg->num_fuse_corners;
break;
case VDD_MX_VMIN_APC_CORNER_MAP:
corner_map_len = cpr_vreg->num_corners;
break;
default:
cpr_vreg->vdd_mx_corner_map = NULL;
return 0;
}
if (!of_find_property(of_node, "qcom,vdd-mx-corner-map", &len)) {
cpr_err(cpr_vreg, "qcom,vdd-mx-corner-map missing");
return -EINVAL;
}
size = len / sizeof(u32);
if (size != corner_map_len) {
cpr_err(cpr_vreg,
"qcom,vdd-mx-corner-map length=%d is invalid: required:%u\n",
size, corner_map_len);
return -EINVAL;
}
cpr_vreg->vdd_mx_corner_map = devm_kzalloc(&pdev->dev,
(corner_map_len + 1) * sizeof(*cpr_vreg->vdd_mx_corner_map),
GFP_KERNEL);
if (!cpr_vreg->vdd_mx_corner_map) {
cpr_err(cpr_vreg,
"Can't allocate memory for cpr_vreg->vdd_mx_corner_map\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node,
"qcom,vdd-mx-corner-map",
&cpr_vreg->vdd_mx_corner_map[1],
corner_map_len);
if (rc)
cpr_err(cpr_vreg,
"read qcom,vdd-mx-corner-map failed, rc = %d\n", rc);
return rc;
}
#define MAX_CHARS_PER_INT 10
/*
* The initial voltage for each fuse corner may be determined by one of two
* possible styles of fuse. If qcom,cpr-fuse-init-voltage is present, then
* the initial voltages are encoded in a fuse for each fuse corner. If it is
* not present, then the initial voltages are all determined using a single
* PVS bin fuse value.
*/
static int cpr_pvs_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int highest_fuse_corner = cpr_vreg->num_fuse_corners;
int i, rc, pos;
size_t buflen;
char *buf;
rc = of_property_read_u32(of_node, "qcom,cpr-apc-volt-step",
&cpr_vreg->step_volt);
if (rc < 0) {
cpr_err(cpr_vreg, "read cpr-apc-volt-step failed, rc = %d\n",
rc);
return rc;
} else if (cpr_vreg->step_volt == 0) {
cpr_err(cpr_vreg, "apc voltage step size can't be set to 0.\n");
return -EINVAL;
}
if (of_find_property(of_node, "qcom,cpr-fuse-init-voltage", NULL)) {
rc = cpr_pvs_per_corner_init(of_node, cpr_vreg);
if (rc < 0) {
cpr_err(cpr_vreg, "get pvs per corner failed, rc = %d",
rc);
return rc;
}
} else {
rc = cpr_pvs_single_bin_init(of_node, cpr_vreg);
if (rc < 0) {
cpr_err(cpr_vreg,
"get pvs from single bin failed, rc = %d", rc);
return rc;
}
}
if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) {
rc = cpr_voltage_uplift_wa_inc_volt(cpr_vreg, of_node);
if (rc < 0) {
cpr_err(cpr_vreg, "pvs volt uplift wa apply failed: %d",
rc);
return rc;
}
}
/*
* Allow the highest fuse corner's PVS voltage to define the ceiling
* voltage for that corner in order to support SoC's in which variable
* ceiling values are required.
*/
if (cpr_vreg->pvs_corner_v[highest_fuse_corner] >
cpr_vreg->fuse_ceiling_volt[highest_fuse_corner])
cpr_vreg->fuse_ceiling_volt[highest_fuse_corner] =
cpr_vreg->pvs_corner_v[highest_fuse_corner];
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
/*
* Restrict all fuse corner PVS voltages based upon per corner
* ceiling and floor voltages.
*/
for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
if (cpr_vreg->pvs_corner_v[i] > cpr_vreg->fuse_ceiling_volt[i])
cpr_vreg->pvs_corner_v[i]
= cpr_vreg->fuse_ceiling_volt[i];
else if (cpr_vreg->pvs_corner_v[i]
< cpr_vreg->fuse_floor_volt[i])
cpr_vreg->pvs_corner_v[i]
= cpr_vreg->fuse_floor_volt[i];
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
cpr_vreg->ceiling_max
= cpr_vreg->fuse_ceiling_volt[highest_fuse_corner];
/*
* Log ceiling, floor, and inital voltages since they are critical for
* all CPR debugging.
*/
buflen = cpr_vreg->num_fuse_corners * (MAX_CHARS_PER_INT + 2)
* sizeof(*buf);
buf = kzalloc(buflen, GFP_KERNEL);
if (buf == NULL) {
cpr_err(cpr_vreg, "Could not allocate memory for corner voltage logging\n");
return 0;
}
for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
pos += scnprintf(buf + pos, buflen - pos, "%u%s",
cpr_vreg->pvs_corner_v[i],
i < highest_fuse_corner ? " " : "");
cpr_info(cpr_vreg, "pvs voltage: [%s] uV\n", buf);
for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
pos += scnprintf(buf + pos, buflen - pos, "%d%s",
cpr_vreg->fuse_ceiling_volt[i],
i < highest_fuse_corner ? " " : "");
cpr_info(cpr_vreg, "ceiling voltage: [%s] uV\n", buf);
for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
pos += scnprintf(buf + pos, buflen - pos, "%d%s",
cpr_vreg->fuse_floor_volt[i],
i < highest_fuse_corner ? " " : "");
cpr_info(cpr_vreg, "floor voltage: [%s] uV\n", buf);
kfree(buf);
return 0;
}
#define CPR_PROP_READ_U32(cpr_vreg, of_node, cpr_property, cpr_config, rc) \
do { \
if (!rc) { \
rc = of_property_read_u32(of_node, \
"qcom," cpr_property, \
cpr_config); \
if (rc) { \
cpr_err(cpr_vreg, "Missing " #cpr_property \
": rc = %d\n", rc); \
} \
} \
} while (0)
static int cpr_apc_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int i, rc = 0;
for (i = 0; i < ARRAY_SIZE(vdd_apc_name); i++) {
cpr_vreg->vdd_apc = devm_regulator_get(&pdev->dev,
vdd_apc_name[i]);
rc = PTR_RET(cpr_vreg->vdd_apc);
if (!IS_ERR_OR_NULL(cpr_vreg->vdd_apc))
break;
}
if (rc) {
if (rc != -EPROBE_DEFER)
cpr_err(cpr_vreg, "devm_regulator_get: rc=%d\n", rc);
return rc;
}
/* Check dependencies */
if (of_find_property(of_node, "vdd-mx-supply", NULL)) {
cpr_vreg->vdd_mx = devm_regulator_get(&pdev->dev, "vdd-mx");
if (IS_ERR_OR_NULL(cpr_vreg->vdd_mx)) {
rc = PTR_RET(cpr_vreg->vdd_mx);
if (rc != -EPROBE_DEFER)
cpr_err(cpr_vreg,
"devm_regulator_get: vdd-mx: rc=%d\n",
rc);
return rc;
}
}
return 0;
}
static void cpr_apc_exit(struct cpr_regulator *cpr_vreg)
{
if (cpr_vreg->vreg_enabled) {
regulator_disable(cpr_vreg->vdd_apc);
if (cpr_vreg->vdd_mx)
regulator_disable(cpr_vreg->vdd_mx);
}
}
static int cpr_voltage_uplift_wa_inc_quot(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
u32 delta_quot[3];
int rc, i;
rc = of_property_read_u32_array(of_node,
"qcom,cpr-uplift-quotient", delta_quot, 3);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr-uplift-quotient is missing: %d", rc);
return rc;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
cpr_vreg->cpr_fuse_target_quot[i] += delta_quot[i-1];
return rc;
}
static void cpr_parse_pvs_version_fuse(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
int rc;
u64 fuse_bits;
u32 fuse_sel[4];
rc = of_property_read_u32_array(of_node,
"qcom,pvs-version-fuse-sel", fuse_sel, 4);
if (!rc) {
fuse_bits = cpr_read_efuse_row(cpr_vreg,
fuse_sel[0], fuse_sel[3]);
cpr_vreg->pvs_version = (fuse_bits >> fuse_sel[1]) &
((1 << fuse_sel[2]) - 1);
cpr_info(cpr_vreg, "[row: %d]: 0x%llx, pvs_version = %d\n",
fuse_sel[0], fuse_bits, cpr_vreg->pvs_version);
} else {
cpr_vreg->pvs_version = 0;
}
}
/**
* cpr_get_open_loop_voltage() - fill the open_loop_volt array with linearly
* interpolated open-loop CPR voltage values.
* @cpr_vreg: Handle to the cpr-regulator device
* @dev: Device pointer for the cpr-regulator device
* @corner_max: Array of length (cpr_vreg->num_fuse_corners + 1) which maps from
* fuse corners to the highest virtual corner corresponding to a
* given fuse corner
* @freq_map: Array of length (cpr_vreg->num_corners + 1) which maps from
* virtual corners to frequencies in Hz.
* @maps_valid: Boolean which indicates if the values in corner_max and freq_map
* are valid. If they are not valid, then the open_loop_volt
* values are not interpolated.
*/
static int cpr_get_open_loop_voltage(struct cpr_regulator *cpr_vreg,
struct device *dev, const u32 *corner_max, const u32 *freq_map,
bool maps_valid)
{
int rc = 0;
int i, j;
u64 volt_high, volt_low, freq_high, freq_low, freq, temp, temp_limit;
u32 *max_factor = NULL;
cpr_vreg->open_loop_volt = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_corners + 1), GFP_KERNEL);
if (!cpr_vreg->open_loop_volt) {
cpr_err(cpr_vreg,
"Can't allocate memory for cpr_vreg->open_loop_volt\n");
return -ENOMEM;
}
/*
* Set open loop voltage to be equal to per-fuse-corner initial voltage
* by default. This ensures that the open loop voltage is valid for
* all virtual corners even if some virtual corner to frequency mappings
* are missing. It also ensures that the voltage is valid for the
* higher corners not utilized by a given speed-bin.
*/
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
cpr_vreg->open_loop_volt[i]
= cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]];
if (!maps_valid || !corner_max || !freq_map
|| !of_find_property(dev->of_node,
"qcom,cpr-voltage-scaling-factor-max", NULL)) {
/* Not using interpolation */
return 0;
}
max_factor
= kzalloc(sizeof(*max_factor) * (cpr_vreg->num_fuse_corners + 1),
GFP_KERNEL);
if (!max_factor) {
cpr_err(cpr_vreg, "Could not allocate memory for max_factor array\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-voltage-scaling-factor-max",
&max_factor[CPR_FUSE_CORNER_MIN],
cpr_vreg->num_fuse_corners);
if (rc) {
cpr_debug(cpr_vreg, "failed to read qcom,cpr-voltage-scaling-factor-max; initial voltage interpolation not possible\n");
kfree(max_factor);
return 0;
}
for (j = CPR_FUSE_CORNER_MIN + 1; j <= cpr_vreg->num_fuse_corners;
j++) {
freq_high = freq_map[corner_max[j]];
freq_low = freq_map[corner_max[j - 1]];
volt_high = cpr_vreg->pvs_corner_v[j];
volt_low = cpr_vreg->pvs_corner_v[j - 1];
if (freq_high <= freq_low || volt_high <= volt_low)
continue;
for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) {
freq = freq_map[i];
if (freq_high <= freq)
continue;
temp = (freq_high - freq) * (volt_high - volt_low);
do_div(temp, (u32)(freq_high - freq_low));
/*
* max_factor[j] has units of uV/MHz while freq values
* have units of Hz. Divide by 1000000 to convert.
*/
temp_limit = (freq_high - freq) * max_factor[j];
do_div(temp_limit, 1000000);
cpr_vreg->open_loop_volt[i]
= volt_high - min(temp, temp_limit);
cpr_vreg->open_loop_volt[i]
= DIV_ROUND_UP(cpr_vreg->open_loop_volt[i],
cpr_vreg->step_volt)
* cpr_vreg->step_volt;
}
}
kfree(max_factor);
return 0;
}
/*
* Limit the per-virtual-corner open-loop voltages using the per-virtual-corner
* ceiling and floor voltage values. This must be called only after the
* open_loop_volt, ceiling, and floor arrays have all been initialized.
*/
static int cpr_limit_open_loop_voltage(struct cpr_regulator *cpr_vreg)
{
int i;
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
if (cpr_vreg->open_loop_volt[i] > cpr_vreg->ceiling_volt[i])
cpr_vreg->open_loop_volt[i] = cpr_vreg->ceiling_volt[i];
else if (cpr_vreg->open_loop_volt[i] < cpr_vreg->floor_volt[i])
cpr_vreg->open_loop_volt[i] = cpr_vreg->floor_volt[i];
}
return 0;
}
/*
* Fill an OPP table for the cpr-regulator device struct with pairs of
* <virtual voltage corner number, open loop voltage> tuples.
*/
static int cpr_populate_opp_table(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int i, rc = 0;
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
rc |= dev_pm_opp_add(dev, i, cpr_vreg->open_loop_volt[i]);
if (rc)
cpr_debug(cpr_vreg, "could not add OPP entry <%d, %d>, rc=%d\n",
i, cpr_vreg->open_loop_volt[i], rc);
}
if (rc)
cpr_err(cpr_vreg, "adding OPP entry failed - OPP may not be enabled, rc=%d\n",
rc);
return 0;
}
/*
* Conditionally reduce the per-virtual-corner ceiling voltages if certain
* device tree flags are present. This must be called only after the ceiling
* array has been initialized and the open_loop_volt array values have been
* initialized and limited to the existing floor to ceiling voltage range.
*/
static int cpr_reduce_ceiling_voltage(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
bool reduce_to_fuse_open_loop, reduce_to_interpolated_open_loop;
int i;
reduce_to_fuse_open_loop = of_property_read_bool(dev->of_node,
"qcom,cpr-init-voltage-as-ceiling");
reduce_to_interpolated_open_loop = of_property_read_bool(dev->of_node,
"qcom,cpr-scaled-init-voltage-as-ceiling");
if (!reduce_to_fuse_open_loop && !reduce_to_interpolated_open_loop)
return 0;
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
if (reduce_to_interpolated_open_loop &&
cpr_vreg->open_loop_volt[i] < cpr_vreg->ceiling_volt[i])
cpr_vreg->ceiling_volt[i] = cpr_vreg->open_loop_volt[i];
else if (reduce_to_fuse_open_loop &&
cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]
< cpr_vreg->ceiling_volt[i])
cpr_vreg->ceiling_volt[i]
= max((u32)cpr_vreg->floor_volt[i],
cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]);
cpr_debug(cpr_vreg, "lowered ceiling[%d] = %d uV\n",
i, cpr_vreg->ceiling_volt[i]);
}
return 0;
}
static int cpr_adjust_target_quot_offsets(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int tuple_count, tuple_match, i;
u32 index;
u32 quot_offset_adjust = 0;
int len = 0;
int rc = 0;
char *quot_offset_str;
quot_offset_str = "qcom,cpr-quot-offset-adjustment";
if (!of_find_property(of_node, quot_offset_str, &len)) {
/* No static quotient adjustment needed. */
return 0;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for quotient adjustment. */
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 (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "%s length=%d is invalid\n", quot_offset_str,
len);
return -EINVAL;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
index = tuple_match * cpr_vreg->num_fuse_corners
+ i - CPR_FUSE_CORNER_MIN;
rc = of_property_read_u32_index(of_node, quot_offset_str, index,
&quot_offset_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
quot_offset_str, index, rc);
return rc;
}
if (quot_offset_adjust) {
cpr_vreg->fuse_quot_offset[i] += quot_offset_adjust;
cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n",
i, cpr_vreg->fuse_quot_offset[i]);
}
}
return rc;
}
static int cpr_get_fuse_quot_offset(struct cpr_regulator *cpr_vreg,
struct platform_device *pdev,
struct cpr_quot_scale *quot_scale)
{
struct device *dev = &pdev->dev;
struct property *prop;
u32 *fuse_sel, *tmp, *offset_multiplier = NULL;
int rc = 0, i, size, len;
char *quot_offset_str;
quot_offset_str = cpr_vreg->cpr_fuse_redundant
? "qcom,cpr-fuse-redun-quot-offset"
: "qcom,cpr-fuse-quot-offset";
prop = of_find_property(dev->of_node, quot_offset_str, NULL);
if (!prop) {
cpr_debug(cpr_vreg, "%s not present\n", quot_offset_str);
return 0;
} else {
size = prop->length / sizeof(u32);
if (size != cpr_vreg->num_fuse_corners * 4) {
cpr_err(cpr_vreg, "fuse position for quot offset is invalid\n");
return -EINVAL;
}
}
fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL);
if (!fuse_sel) {
cpr_err(cpr_vreg, "memory alloc failed.\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(dev->of_node, quot_offset_str,
fuse_sel, size);
if (rc < 0) {
cpr_err(cpr_vreg, "read %s failed, rc = %d\n", quot_offset_str,
rc);
kfree(fuse_sel);
return rc;
}
cpr_vreg->fuse_quot_offset = devm_kzalloc(dev,
sizeof(u32) * (cpr_vreg->num_fuse_corners + 1),
GFP_KERNEL);
if (!cpr_vreg->fuse_quot_offset) {
cpr_err(cpr_vreg, "Can't allocate memory for cpr_vreg->fuse_quot_offset\n");
kfree(fuse_sel);
return -ENOMEM;
}
if (!of_find_property(dev->of_node,
"qcom,cpr-fuse-quot-offset-scale", &len)) {
cpr_debug(cpr_vreg, "qcom,cpr-fuse-quot-offset-scale not present\n");
} else {
if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) {
cpr_err(cpr_vreg, "the size of qcom,cpr-fuse-quot-offset-scale is invalid\n");
kfree(fuse_sel);
return -EINVAL;
}
offset_multiplier = kzalloc(sizeof(*offset_multiplier)
* (cpr_vreg->num_fuse_corners + 1),
GFP_KERNEL);
if (!offset_multiplier) {
cpr_err(cpr_vreg, "memory alloc failed.\n");
kfree(fuse_sel);
return -ENOMEM;
}
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-fuse-quot-offset-scale",
&offset_multiplier[1],
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "read qcom,cpr-fuse-quot-offset-scale failed, rc = %d\n",
rc);
kfree(fuse_sel);
goto out;
}
}
tmp = fuse_sel;
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
cpr_vreg->fuse_quot_offset[i] = cpr_read_efuse_param(cpr_vreg,
fuse_sel[0], fuse_sel[1], fuse_sel[2],
fuse_sel[3]);
if (offset_multiplier)
cpr_vreg->fuse_quot_offset[i] *= offset_multiplier[i];
fuse_sel += 4;
}
rc = cpr_adjust_target_quot_offsets(pdev, cpr_vreg);
kfree(tmp);
out:
kfree(offset_multiplier);
return rc;
}
/*
* Adjust the per-virtual-corner open loop voltage with an offset specfied by a
* device-tree property. This must be called after open-loop voltage scaling.
*/
static int cpr_virtual_corner_voltage_adjust(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
char *prop_name = "qcom,cpr-virtual-corner-init-voltage-adjustment";
int i, rc, tuple_count, tuple_match, index, len;
u32 voltage_adjust;
if (!of_find_property(dev->of_node, prop_name, &len)) {
cpr_debug(cpr_vreg, "%s not specified\n", prop_name);
return 0;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for voltage adjustment. */
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 (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name,
len);
return -EINVAL;
}
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
index = tuple_match * cpr_vreg->num_corners
+ i - CPR_CORNER_MIN;
rc = of_property_read_u32_index(dev->of_node, prop_name,
index, &voltage_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
prop_name, index, rc);
return rc;
}
if (voltage_adjust) {
cpr_vreg->open_loop_volt[i] += (int)voltage_adjust;
cpr_info(cpr_vreg, "corner=%d adjusted open-loop voltage=%d\n",
i, cpr_vreg->open_loop_volt[i]);
}
}
return 0;
}
/*
* Adjust the per-virtual-corner quot with an offset specfied by a
* device-tree property. This must be called after the quot-scaling adjustments
* are completed.
*/
static int cpr_virtual_corner_quot_adjust(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
char *prop_name = "qcom,cpr-virtual-corner-quotient-adjustment";
int i, rc, tuple_count, tuple_match, index, len;
u32 quot_adjust;
if (!of_find_property(dev->of_node, prop_name, &len)) {
cpr_debug(cpr_vreg, "%s not specified\n", prop_name);
return 0;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for quotient adjustment. */
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 (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name,
len);
return -EINVAL;
}
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
index = tuple_match * cpr_vreg->num_corners
+ i - CPR_CORNER_MIN;
rc = of_property_read_u32_index(dev->of_node, prop_name,
index, &quot_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
prop_name, index, rc);
return rc;
}
if (quot_adjust) {
cpr_vreg->quot_adjust[i] -= (int)quot_adjust;
cpr_info(cpr_vreg, "corner=%d adjusted quotient=%d\n",
i,
cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]]
- cpr_vreg->quot_adjust[i]);
}
}
return 0;
}
/*
* cpr_get_corner_quot_adjustment() -- get the quot_adjust for each corner.
*
* Get the virtual corner to fuse corner mapping and virtual corner to APC clock
* frequency mapping from device tree.
* Calculate the quotient adjustment scaling factor for those corners mapping to
* all fuse corners except for the lowest one using linear interpolation.
* Calculate the quotient adjustment for each of these virtual corners using the
* min of the calculated scaling factor and the constant max scaling factor
* defined for each fuse corner in device tree.
*/
static int cpr_get_corner_quot_adjustment(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int rc = 0;
int highest_fuse_corner = cpr_vreg->num_fuse_corners;
int i, j, size;
struct property *prop;
bool corners_mapped, match_found;
u32 *tmp, *freq_map = NULL;
u32 corner, freq_corner;
u32 *freq_max = NULL;
u32 *scaling = NULL;
u32 *max_factor = NULL;
u32 *corner_max = NULL;
bool maps_valid = false;
prop = of_find_property(dev->of_node, "qcom,cpr-corner-map", NULL);
if (prop) {
size = prop->length / sizeof(u32);
corners_mapped = true;
} else {
size = cpr_vreg->num_fuse_corners;
corners_mapped = false;
}
cpr_vreg->corner_map = devm_kzalloc(dev, sizeof(int) * (size + 1),
GFP_KERNEL);
if (!cpr_vreg->corner_map) {
cpr_err(cpr_vreg,
"Can't allocate memory for cpr_vreg->corner_map\n");
return -ENOMEM;
}
cpr_vreg->num_corners = size;
cpr_vreg->quot_adjust = devm_kzalloc(dev,
sizeof(u32) * (cpr_vreg->num_corners + 1),
GFP_KERNEL);
if (!cpr_vreg->quot_adjust) {
cpr_err(cpr_vreg,
"Can't allocate memory for cpr_vreg->quot_adjust\n");
return -ENOMEM;
}
if (!corners_mapped) {
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++)
cpr_vreg->corner_map[i] = i;
goto free_arrays;
} else {
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-corner-map", &cpr_vreg->corner_map[1], size);
if (rc) {
cpr_err(cpr_vreg,
"qcom,cpr-corner-map missing, rc = %d\n", rc);
return rc;
}
/*
* Verify that the virtual corner to fuse corner mapping is
* valid.
*/
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
if (cpr_vreg->corner_map[i] > cpr_vreg->num_fuse_corners
|| cpr_vreg->corner_map[i] < CPR_FUSE_CORNER_MIN) {
cpr_err(cpr_vreg, "qcom,cpr-corner-map contains an element %d which isn't in the allowed range [%d, %d]\n",
cpr_vreg->corner_map[i],
CPR_FUSE_CORNER_MIN,
cpr_vreg->num_fuse_corners);
return -EINVAL;
}
}
}
prop = of_find_property(dev->of_node,
"qcom,cpr-speed-bin-max-corners", NULL);
if (!prop) {
cpr_debug(cpr_vreg, "qcom,cpr-speed-bin-max-corner missing\n");
goto free_arrays;
}
size = prop->length / sizeof(u32);
tmp = kzalloc(size * sizeof(u32), GFP_KERNEL);
if (!tmp) {
cpr_err(cpr_vreg, "memory alloc failed\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-speed-bin-max-corners", tmp, size);
if (rc < 0) {
kfree(tmp);
cpr_err(cpr_vreg,
"get cpr-speed-bin-max-corners failed, rc = %d\n", rc);
return rc;
}
corner_max = kzalloc((cpr_vreg->num_fuse_corners + 1)
* sizeof(*corner_max), GFP_KERNEL);
freq_max = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*freq_max),
GFP_KERNEL);
if (corner_max == NULL || freq_max == NULL) {
cpr_err(cpr_vreg, "Could not allocate memory for quotient scaling arrays\n");
kfree(tmp);
rc = -ENOMEM;
goto free_arrays;
}
/*
* Get the maximum virtual corner for each fuse corner based upon the
* speed_bin and pvs_version values.
*/
match_found = false;
for (i = 0; i < size; i += cpr_vreg->num_fuse_corners + 2) {
if (tmp[i] != cpr_vreg->speed_bin &&
tmp[i] != FUSE_PARAM_MATCH_ANY)
continue;
if (tmp[i + 1] != cpr_vreg->pvs_version &&
tmp[i + 1] != FUSE_PARAM_MATCH_ANY)
continue;
for (j = CPR_FUSE_CORNER_MIN;
j <= cpr_vreg->num_fuse_corners; j++)
corner_max[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN];
match_found = true;
break;
}
kfree(tmp);
if (!match_found) {
cpr_debug(cpr_vreg, "No quotient adjustment possible for speed bin=%u, pvs version=%u\n",
cpr_vreg->speed_bin, cpr_vreg->pvs_version);
goto free_arrays;
}
/* Verify that fuse corner to max virtual corner mapping is valid. */
for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) {
if (corner_max[i] < CPR_CORNER_MIN
|| corner_max[i] > cpr_vreg->num_corners) {
cpr_err(cpr_vreg, "Invalid corner=%d in qcom,cpr-speed-bin-max-corners\n",
corner_max[i]);
goto free_arrays;
}
}
/*
* Return success if the virtual corner values read from
* qcom,cpr-speed-bin-max-corners property are incorrect. This allows
* the driver to continue to run without quotient scaling.
*/
for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
if (corner_max[i] <= corner_max[i - 1]) {
cpr_err(cpr_vreg, "fuse corner=%d (%u) should be larger than the fuse corner=%d (%u)\n",
i, corner_max[i], i - 1, corner_max[i - 1]);
goto free_arrays;
}
}
prop = of_find_property(dev->of_node,
"qcom,cpr-corner-frequency-map", NULL);
if (!prop) {
cpr_debug(cpr_vreg, "qcom,cpr-corner-frequency-map missing\n");
goto free_arrays;
}
size = prop->length / sizeof(u32);
tmp = kzalloc(sizeof(u32) * size, GFP_KERNEL);
if (!tmp) {
cpr_err(cpr_vreg, "memory alloc failed\n");
rc = -ENOMEM;
goto free_arrays;
}
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-corner-frequency-map", tmp, size);
if (rc < 0) {
cpr_err(cpr_vreg,
"get cpr-corner-frequency-map failed, rc = %d\n", rc);
kfree(tmp);
goto free_arrays;
}
freq_map = kzalloc(sizeof(u32) * (cpr_vreg->num_corners + 1),
GFP_KERNEL);
if (!freq_map) {
cpr_err(cpr_vreg, "memory alloc for freq_map failed!\n");
kfree(tmp);
rc = -ENOMEM;
goto free_arrays;
}
for (i = 0; i < size; i += 2) {
corner = tmp[i];
if ((corner < 1) || (corner > cpr_vreg->num_corners)) {
cpr_err(cpr_vreg,
"corner should be in 1~%d range: %d\n",
cpr_vreg->num_corners, corner);
continue;
}
freq_map[corner] = tmp[i + 1];
cpr_debug(cpr_vreg,
"Frequency at virtual corner %d is %d Hz.\n",
corner, freq_map[corner]);
}
kfree(tmp);
prop = of_find_property(dev->of_node,
"qcom,cpr-quot-adjust-scaling-factor-max", NULL);
if (!prop) {
cpr_debug(cpr_vreg, "qcom,cpr-quot-adjust-scaling-factor-max missing\n");
rc = 0;
goto free_arrays;
}
size = prop->length / sizeof(u32);
if ((size != 1) && (size != cpr_vreg->num_fuse_corners)) {
cpr_err(cpr_vreg, "The size of qcom,cpr-quot-adjust-scaling-factor-max should be 1 or %d\n",
cpr_vreg->num_fuse_corners);
rc = 0;
goto free_arrays;
}
max_factor = kzalloc(sizeof(u32) * (cpr_vreg->num_fuse_corners + 1),
GFP_KERNEL);
if (!max_factor) {
cpr_err(cpr_vreg, "Could not allocate memory for max_factor array\n");
rc = -ENOMEM;
goto free_arrays;
}
/*
* Leave max_factor[CPR_FUSE_CORNER_MIN ... highest_fuse_corner-1] = 0
* if cpr-quot-adjust-scaling-factor-max is a single value in order to
* maintain backward compatibility.
*/
i = (size == cpr_vreg->num_fuse_corners) ? CPR_FUSE_CORNER_MIN
: highest_fuse_corner;
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-quot-adjust-scaling-factor-max",
&max_factor[i], size);
if (rc < 0) {
cpr_debug(cpr_vreg, "could not read qcom,cpr-quot-adjust-scaling-factor-max, rc=%d\n",
rc);
rc = 0;
goto free_arrays;
}
/*
* Get the quotient adjustment scaling factor, according to:
* scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1))
* / (freq(corner_N) - freq(corner_N-1)), max_factor)
*
* QUOT(corner_N): quotient read from fuse for fuse corner N
* QUOT(corner_N-1): quotient read from fuse for fuse corner (N - 1)
* freq(corner_N): max frequency in MHz supported by fuse corner N
* freq(corner_N-1): max frequency in MHz supported by fuse corner
* (N - 1)
*/
for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
freq_max[i] = freq_map[corner_max[i]];
for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
if (freq_max[i] <= freq_max[i - 1] || freq_max[i - 1] == 0) {
cpr_err(cpr_vreg, "fuse corner %d freq=%u should be larger than fuse corner %d freq=%u\n",
i, freq_max[i], i - 1, freq_max[i - 1]);
rc = -EINVAL;
goto free_arrays;
}
}
scaling = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*scaling),
GFP_KERNEL);
if (!scaling) {
cpr_err(cpr_vreg, "Could not allocate memory for scaling array\n");
rc = -ENOMEM;
goto free_arrays;
}
/* Convert corner max frequencies from Hz to MHz. */
for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
freq_max[i] /= 1000000;
for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
if (cpr_vreg->fuse_quot_offset &&
(cpr_vreg->cpr_fuse_ro_sel[i] !=
cpr_vreg->cpr_fuse_ro_sel[i - 1])) {
scaling[i] = 1000 * cpr_vreg->fuse_quot_offset[i]
/ (freq_max[i] - freq_max[i - 1]);
} else {
scaling[i] = 1000 * (cpr_vreg->cpr_fuse_target_quot[i]
- cpr_vreg->cpr_fuse_target_quot[i - 1])
/ (freq_max[i] - freq_max[i - 1]);
if (cpr_vreg->cpr_fuse_target_quot[i]
< cpr_vreg->cpr_fuse_target_quot[i - 1])
scaling[i] = 0;
}
scaling[i] = min(scaling[i], max_factor[i]);
cpr_info(cpr_vreg, "fuse corner %d quotient adjustment scaling factor: %d.%03d\n",
i, scaling[i] / 1000, scaling[i] % 1000);
}
/*
* Walk through the virtual corners mapped to each fuse corner
* and calculate the quotient adjustment for each one using the
* following formula:
* quot_adjust = (freq_max - freq_corner) * scaling / 1000
*
* @freq_max: max frequency in MHz supported by the fuse corner
* @freq_corner: frequency in MHz corresponding to the virtual corner
*/
for (j = CPR_FUSE_CORNER_MIN + 1; j <= highest_fuse_corner; j++) {
for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) {
freq_corner = freq_map[i] / 1000000; /* MHz */
if (freq_corner > 0) {
cpr_vreg->quot_adjust[i] = scaling[j] *
(freq_max[j] - freq_corner) / 1000;
}
}
}
rc = cpr_virtual_corner_quot_adjust(cpr_vreg, dev);
if (rc) {
cpr_err(cpr_vreg, "count not adjust virtual-corner quot rc=%d\n",
rc);
goto free_arrays;
}
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
cpr_info(cpr_vreg, "adjusted quotient[%d] = %d\n", i,
cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]]
- cpr_vreg->quot_adjust[i]);
maps_valid = true;
free_arrays:
if (!rc) {
rc = cpr_get_open_loop_voltage(cpr_vreg, dev, corner_max,
freq_map, maps_valid);
if (rc) {
cpr_err(cpr_vreg, "could not fill open loop voltage array, rc=%d\n",
rc);
goto free_arrays_1;
}
rc = cpr_virtual_corner_voltage_adjust(cpr_vreg, dev);
if (rc)
cpr_err(cpr_vreg, "count not adjust virtual-corner voltage rc=%d\n",
rc);
}
free_arrays_1:
kfree(max_factor);
kfree(scaling);
kfree(freq_map);
kfree(corner_max);
kfree(freq_max);
return rc;
}
/*
* Check if the redundant set of CPR fuses should be used in place of the
* primary set and configure the cpr_fuse_redundant element accordingly.
*/
static int cpr_check_redundant(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
u32 cpr_fuse_redun_sel[5];
int rc;
if (of_find_property(of_node, "qcom,cpr-fuse-redun-sel", NULL)) {
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-redun-sel", cpr_fuse_redun_sel, 5);
if (rc < 0) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-redun-sel missing: rc=%d\n",
rc);
return rc;
}
cpr_vreg->cpr_fuse_redundant
= cpr_fuse_is_setting_expected(cpr_vreg,
cpr_fuse_redun_sel);
} else {
cpr_vreg->cpr_fuse_redundant = false;
}
if (cpr_vreg->cpr_fuse_redundant)
cpr_info(cpr_vreg, "using redundant fuse parameters\n");
return 0;
}
static int cpr_read_fuse_revision(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
u32 fuse_sel[4];
int rc;
if (of_find_property(of_node, "qcom,cpr-fuse-revision", NULL)) {
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-revision", fuse_sel, 4);
if (rc < 0) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-revision read failed: rc=%d\n",
rc);
return rc;
}
cpr_vreg->cpr_fuse_revision
= cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
fuse_sel[1], fuse_sel[2], fuse_sel[3]);
cpr_info(cpr_vreg, "fuse revision = %d\n",
cpr_vreg->cpr_fuse_revision);
} else {
cpr_vreg->cpr_fuse_revision = FUSE_REVISION_UNKNOWN;
}
return 0;
}
static int cpr_read_ro_select(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int rc = 0;
u32 cpr_fuse_row[2];
char *ro_sel_str;
int *bp_ro_sel;
int i;
bp_ro_sel
= kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*bp_ro_sel),
GFP_KERNEL);
if (!bp_ro_sel) {
cpr_err(cpr_vreg, "could not allocate memory for temp array\n");
return -ENOMEM;
}
if (cpr_vreg->cpr_fuse_redundant) {
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-redun-row",
cpr_fuse_row, 2);
ro_sel_str = "qcom,cpr-fuse-redun-ro-sel";
} else {
rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row",
cpr_fuse_row, 2);
ro_sel_str = "qcom,cpr-fuse-ro-sel";
}
if (rc)
goto error;
rc = of_property_read_u32_array(of_node, ro_sel_str,
&bp_ro_sel[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners);
if (rc) {
cpr_err(cpr_vreg, "%s read error, rc=%d\n", ro_sel_str, rc);
goto error;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
cpr_vreg->cpr_fuse_ro_sel[i]
= cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0],
bp_ro_sel[i], CPR_FUSE_RO_SEL_BITS,
cpr_fuse_row[1]);
error:
kfree(bp_ro_sel);
return rc;
}
static int cpr_find_fuse_map_match(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int i, j, rc, tuple_size;
int len = 0;
u32 *tmp, val, ro;
/* Specify default no match case. */
cpr_vreg->cpr_fuse_map_match = FUSE_MAP_NO_MATCH;
cpr_vreg->cpr_fuse_map_count = 0;
if (!of_find_property(of_node, "qcom,cpr-fuse-version-map", &len)) {
/* No mapping present. */
return 0;
}
tuple_size = cpr_vreg->num_fuse_corners + 3;
cpr_vreg->cpr_fuse_map_count = len / (sizeof(u32) * tuple_size);
if (len == 0 || len % (sizeof(u32) * tuple_size)) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-version-map length=%d is invalid\n",
len);
return -EINVAL;
}
tmp = kzalloc(len, GFP_KERNEL);
if (!tmp) {
cpr_err(cpr_vreg, "could not allocate memory for temp array\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-version-map",
tmp, cpr_vreg->cpr_fuse_map_count * tuple_size);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-fuse-version-map, rc=%d\n",
rc);
goto done;
}
/*
* qcom,cpr-fuse-version-map tuple format:
* <speed_bin, pvs_version, cpr_fuse_revision, ro_sel[1], ...,
* ro_sel[n]> for n == number of fuse corners
*/
for (i = 0; i < cpr_vreg->cpr_fuse_map_count; i++) {
if (tmp[i * tuple_size] != cpr_vreg->speed_bin
&& tmp[i * tuple_size] != FUSE_PARAM_MATCH_ANY)
continue;
if (tmp[i * tuple_size + 1] != cpr_vreg->pvs_version
&& tmp[i * tuple_size + 1] != FUSE_PARAM_MATCH_ANY)
continue;
if (tmp[i * tuple_size + 2] != cpr_vreg->cpr_fuse_revision
&& tmp[i * tuple_size + 2] != FUSE_PARAM_MATCH_ANY)
continue;
for (j = 0; j < cpr_vreg->num_fuse_corners; j++) {
val = tmp[i * tuple_size + 3 + j];
ro = cpr_vreg->cpr_fuse_ro_sel[j + CPR_FUSE_CORNER_MIN];
if (val != ro && val != FUSE_PARAM_MATCH_ANY)
break;
}
if (j == cpr_vreg->num_fuse_corners) {
cpr_vreg->cpr_fuse_map_match = i;
break;
}
}
if (cpr_vreg->cpr_fuse_map_match != FUSE_MAP_NO_MATCH)
cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match found: %d\n",
cpr_vreg->cpr_fuse_map_match);
else
cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match not found\n");
done:
kfree(tmp);
return rc;
}
static int cpr_minimum_quot_difference_adjustment(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int tuple_count, tuple_match;
int rc, i, len = 0;
u32 index, adjust_quot = 0;
u32 *min_diff_quot;
if (!of_find_property(of_node, "qcom,cpr-fuse-min-quot-diff", NULL))
/* No conditional adjustment needed on revised quotients. */
return 0;
if (!of_find_property(of_node, "qcom,cpr-min-quot-diff-adjustment",
&len)) {
cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment not specified\n");
return -ENODEV;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH)
/* No matching index to use for quotient adjustment. */
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 (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment length=%d is invalid\n",
len);
return -EINVAL;
}
min_diff_quot = kzalloc(cpr_vreg->num_fuse_corners * sizeof(u32),
GFP_KERNEL);
if (!min_diff_quot) {
cpr_err(cpr_vreg, "memory alloc failed\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-min-quot-diff",
min_diff_quot,
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-min-quot-diff reading failed, rc = %d\n",
rc);
goto error;
}
for (i = CPR_FUSE_CORNER_MIN + 1;
i <= cpr_vreg->num_fuse_corners; i++) {
if ((cpr_vreg->cpr_fuse_target_quot[i]
- cpr_vreg->cpr_fuse_target_quot[i - 1])
<= (int)min_diff_quot[i - CPR_FUSE_CORNER_MIN]) {
index = tuple_match * cpr_vreg->num_fuse_corners
+ i - CPR_FUSE_CORNER_MIN;
rc = of_property_read_u32_index(of_node,
"qcom,cpr-min-quot-diff-adjustment",
index, &adjust_quot);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-min-quot-diff-adjustment index %u, rc=%d\n",
index, rc);
goto error;
}
cpr_vreg->cpr_fuse_target_quot[i]
= cpr_vreg->cpr_fuse_target_quot[i - 1]
+ adjust_quot;
cpr_info(cpr_vreg, "Corner[%d]: revised adjusted quotient = %d\n",
i, cpr_vreg->cpr_fuse_target_quot[i]);
};
}
error:
kfree(min_diff_quot);
return rc;
}
static int cpr_adjust_target_quots(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int tuple_count, tuple_match, i;
u32 index;
u32 quot_adjust = 0;
int len = 0;
int rc = 0;
if (!of_find_property(of_node, "qcom,cpr-quotient-adjustment", &len)) {
/* No static quotient adjustment needed. */
return 0;
}
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for quotient adjustment. */
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 (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "qcom,cpr-quotient-adjustment length=%d is invalid\n",
len);
return -EINVAL;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
index = tuple_match * cpr_vreg->num_fuse_corners
+ i - CPR_FUSE_CORNER_MIN;
rc = of_property_read_u32_index(of_node,
"qcom,cpr-quotient-adjustment", index, &quot_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-quotient-adjustment index %u, rc=%d\n",
index, rc);
return rc;
}
if (quot_adjust) {
cpr_vreg->cpr_fuse_target_quot[i] += quot_adjust;
cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n",
i, cpr_vreg->cpr_fuse_target_quot[i]);
}
}
rc = cpr_minimum_quot_difference_adjustment(pdev, cpr_vreg);
if (rc)
cpr_err(cpr_vreg, "failed to apply minimum quot difference rc=%d\n",
rc);
return rc;
}
static int cpr_check_allowed(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
char *allow_str = "qcom,cpr-allowed";
int rc = 0, count;
int tuple_count, tuple_match;
u32 allow_status;
if (!of_find_property(of_node, allow_str, &count))
/* CPR is allowed for all fuse revisions. */
return 0;
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 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;
}
if (allow_status && !cpr_vreg->cpr_fuse_disable)
cpr_vreg->cpr_fuse_disable = false;
else
cpr_vreg->cpr_fuse_disable = true;
cpr_info(cpr_vreg, "CPR closed loop is %s for fuse revision %d\n",
cpr_vreg->cpr_fuse_disable ? "disabled" : "enabled",
cpr_vreg->cpr_fuse_revision);
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;
}
if (of_find_property(of_node, "qcom,cpr-non-collapsible-sensors",
&len)) {
len = len / sizeof(u32);
if (len <= 0 || len > 32) {
cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors 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_debug(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)
{
struct device_node *of_node = pdev->dev.of_node;
int i, rc = 0;
bool scheme_fuse_valid = false;
bool disable_fuse_valid = false;
char *targ_quot_str;
u32 cpr_fuse_row[2];
u32 bp_cpr_disable, bp_scheme;
size_t len;
int *bp_target_quot;
u64 fuse_bits, fuse_bits_2;
u32 *target_quot_size;
struct cpr_quot_scale *quot_scale;
len = cpr_vreg->num_fuse_corners + 1;
bp_target_quot = kzalloc(len * sizeof(*bp_target_quot), GFP_KERNEL);
target_quot_size = kzalloc(len * sizeof(*target_quot_size), GFP_KERNEL);
quot_scale = kzalloc(len * sizeof(*quot_scale), GFP_KERNEL);
if (!bp_target_quot || !target_quot_size || !quot_scale) {
cpr_err(cpr_vreg,
"Could not allocate memory for fuse parsing arrays\n");
rc = -ENOMEM;
goto error;
}
if (cpr_vreg->cpr_fuse_redundant) {
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-redun-row",
cpr_fuse_row, 2);
targ_quot_str = "qcom,cpr-fuse-redun-target-quot";
} else {
rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row",
cpr_fuse_row, 2);
targ_quot_str = "qcom,cpr-fuse-target-quot";
}
if (rc)
goto error;
rc = of_property_read_u32_array(of_node, targ_quot_str,
&bp_target_quot[CPR_FUSE_CORNER_MIN],
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "missing %s: rc=%d\n", targ_quot_str, rc);
goto error;
}
if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-size", NULL)) {
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-target-quot-size",
&target_quot_size[CPR_FUSE_CORNER_MIN],
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-size: rc=%d\n",
rc);
goto error;
}
} else {
/*
* Default fuse quotient parameter size to match target register
* size.
*/
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++)
target_quot_size[i] = CPR_FUSE_TARGET_QUOT_BITS;
}
if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-scale",
NULL)) {
for (i = 0; i < cpr_vreg->num_fuse_corners; i++) {
rc = of_property_read_u32_index(of_node,
"qcom,cpr-fuse-target-quot-scale", i * 2,
&quot_scale[i + CPR_FUSE_CORNER_MIN].offset);
if (rc < 0) {
cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n",
rc);
goto error;
}
rc = of_property_read_u32_index(of_node,
"qcom,cpr-fuse-target-quot-scale", i * 2 + 1,
&quot_scale[i + CPR_FUSE_CORNER_MIN].multiplier);
if (rc < 0) {
cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n",
rc);
goto error;
}
}
} else {
/*
* In the default case, target quotients require no scaling so
* use offset = 0, multiplier = 1.
*/
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++) {
quot_scale[i].offset = 0;
quot_scale[i].multiplier = 1;
}
}
/* Read the control bits of eFuse */
fuse_bits = cpr_read_efuse_row(cpr_vreg, cpr_fuse_row[0],
cpr_fuse_row[1]);
cpr_info(cpr_vreg, "[row:%d] = 0x%llx\n", cpr_fuse_row[0], fuse_bits);
if (cpr_vreg->cpr_fuse_redundant) {
if (of_find_property(of_node,
"qcom,cpr-fuse-redun-bp-cpr-disable", NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-redun-bp-cpr-disable",
&bp_cpr_disable, rc);
disable_fuse_valid = true;
if (of_find_property(of_node,
"qcom,cpr-fuse-redun-bp-scheme",
NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-redun-bp-scheme",
&bp_scheme, rc);
scheme_fuse_valid = true;
}
if (rc)
goto error;
fuse_bits_2 = fuse_bits;
} else {
u32 temp_row[2];
/* Use original fuse if no optional property */
if (of_find_property(of_node,
"qcom,cpr-fuse-bp-cpr-disable", NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-bp-cpr-disable",
&bp_cpr_disable, rc);
disable_fuse_valid = true;
}
if (of_find_property(of_node,
"qcom,cpr-fuse-bp-scheme",
NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-bp-scheme",
&bp_scheme, rc);
scheme_fuse_valid = true;
}
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-row",
temp_row, 2);
if (rc)
goto error;
fuse_bits_2 = cpr_read_efuse_row(cpr_vreg, temp_row[0],
temp_row[1]);
cpr_info(cpr_vreg, "[original row:%d] = 0x%llx\n",
temp_row[0], fuse_bits_2);
}
} else {
if (of_find_property(of_node, "qcom,cpr-fuse-bp-cpr-disable",
NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-bp-cpr-disable", &bp_cpr_disable, rc);
disable_fuse_valid = true;
}
if (of_find_property(of_node, "qcom,cpr-fuse-bp-scheme",
NULL)) {
CPR_PROP_READ_U32(cpr_vreg, of_node,
"cpr-fuse-bp-scheme", &bp_scheme, rc);
scheme_fuse_valid = true;
}
if (rc)
goto error;
fuse_bits_2 = fuse_bits;
}
if (disable_fuse_valid) {
cpr_vreg->cpr_fuse_disable =
(fuse_bits_2 >> bp_cpr_disable) & 0x01;
cpr_info(cpr_vreg, "CPR disable fuse = %d\n",
cpr_vreg->cpr_fuse_disable);
} else {
cpr_vreg->cpr_fuse_disable = false;
}
if (scheme_fuse_valid) {
cpr_vreg->cpr_fuse_local = (fuse_bits_2 >> bp_scheme) & 0x01;
cpr_info(cpr_vreg, "local = %d\n", cpr_vreg->cpr_fuse_local);
} else {
cpr_vreg->cpr_fuse_local = true;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
cpr_vreg->cpr_fuse_target_quot[i]
= cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0],
bp_target_quot[i], target_quot_size[i],
cpr_fuse_row[1]);
/* Unpack the target quotient by scaling. */
cpr_vreg->cpr_fuse_target_quot[i] *= quot_scale[i].multiplier;
cpr_vreg->cpr_fuse_target_quot[i] += quot_scale[i].offset;
cpr_info(cpr_vreg,
"Corner[%d]: ro_sel = %d, target quot = %d\n", i,
cpr_vreg->cpr_fuse_ro_sel[i],
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;
for (i = CPR_FUSE_CORNER_MIN + 1;
i <= cpr_vreg->num_fuse_corners; i++) {
if (cpr_vreg->cpr_fuse_target_quot[i]
< cpr_vreg->cpr_fuse_target_quot[i - 1] &&
cpr_vreg->cpr_fuse_ro_sel[i] ==
cpr_vreg->cpr_fuse_ro_sel[i - 1]) {
cpr_vreg->cpr_fuse_disable = true;
cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n");
}
}
if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) {
cpr_voltage_uplift_wa_inc_quot(cpr_vreg, of_node);
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++) {
cpr_info(cpr_vreg,
"Corner[%d]: uplifted target quot = %d\n",
i, cpr_vreg->cpr_fuse_target_quot[i]);
}
}
/*
* Check whether the fuse-quot-offset is defined per fuse corner.
* If it is defined, use it (quot_offset) in the calculation
* below for obtaining scaling factor per fuse corner.
*/
rc = cpr_get_fuse_quot_offset(cpr_vreg, pdev, quot_scale);
if (rc < 0)
goto error;
rc = cpr_get_corner_quot_adjustment(cpr_vreg, &pdev->dev);
if (rc)
goto error;
cpr_vreg->cpr_fuse_bits = fuse_bits;
if (!cpr_vreg->cpr_fuse_bits) {
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_vreg->cpr_fuse_disable = true;
cpr_err(cpr_vreg,
"cpr_fuse_bits == 0; permanently disabling CPR\n");
} else if (!cpr_vreg->fuse_quot_offset) {
/*
* Check if the target quotients for the highest two fuse
* corners are too close together.
*/
int *quot = cpr_vreg->cpr_fuse_target_quot;
int highest_fuse_corner = cpr_vreg->num_fuse_corners;
u32 min_diff_quot;
bool valid_fuse = true;
min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF;
of_property_read_u32(of_node, "qcom,cpr-quot-min-diff",
&min_diff_quot);
if (quot[highest_fuse_corner] > quot[highest_fuse_corner - 1]) {
if ((quot[highest_fuse_corner]
- quot[highest_fuse_corner - 1])
<= min_diff_quot)
valid_fuse = false;
} else {
valid_fuse = false;
}
if (!valid_fuse) {
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_vreg->cpr_fuse_disable = true;
cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n");
}
}
rc = cpr_check_allowed(pdev, cpr_vreg);
error:
kfree(bp_target_quot);
kfree(target_quot_size);
kfree(quot_scale);
return rc;
}
static int cpr_init_cpr_voltages(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int i;
int size = cpr_vreg->num_corners + 1;
cpr_vreg->last_volt = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
if (!cpr_vreg->last_volt)
return -EINVAL;
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
cpr_vreg->last_volt[i] = cpr_vreg->open_loop_volt[i];
return 0;
}
/*
* This function fills the virtual_limit array with voltages read from the
* prop_name device tree property if a given tuple in the property matches
* the speedbin and PVS version fuses found on the chip. Otherwise,
* it fills the virtual_limit_array with corresponding values from the
* fuse_limit_array.
*/
static int cpr_fill_override_voltage(struct cpr_regulator *cpr_vreg,
struct device *dev, const char *prop_name, const char *label,
int *virtual_limit, int *fuse_limit)
{
int rc = 0;
int i, j, size, pos;
struct property *prop;
bool match_found = false;
size_t buflen;
char *buf;
u32 *tmp;
prop = of_find_property(dev->of_node, prop_name, NULL);
if (!prop)
goto use_fuse_corner_limits;
size = prop->length / sizeof(u32);
if (size == 0 || size % (cpr_vreg->num_corners + 2)) {
cpr_err(cpr_vreg, "%s property format is invalid; reusing per-fuse-corner limits\n",
prop_name);
goto use_fuse_corner_limits;
}
tmp = kzalloc(size * sizeof(u32), GFP_KERNEL);
if (!tmp) {
cpr_err(cpr_vreg, "memory alloc failed\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(dev->of_node, prop_name, tmp, size);
if (rc < 0) {
kfree(tmp);
cpr_err(cpr_vreg, "%s reading failed, rc = %d\n", prop_name,
rc);
return rc;
}
/*
* Get limit voltage for each virtual corner based upon the speed_bin
* and pvs_version values.
*/
for (i = 0; i < size; i += cpr_vreg->num_corners + 2) {
if (tmp[i] != cpr_vreg->speed_bin &&
tmp[i] != FUSE_PARAM_MATCH_ANY)
continue;
if (tmp[i + 1] != cpr_vreg->pvs_version &&
tmp[i + 1] != FUSE_PARAM_MATCH_ANY)
continue;
for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++)
virtual_limit[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN];
match_found = true;
break;
}
kfree(tmp);
if (!match_found)
goto use_fuse_corner_limits;
/*
* Log per-virtual-corner voltage limits since they are useful for
* baseline CPR debugging.
*/
buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf);
buf = kzalloc(buflen, GFP_KERNEL);
if (buf == NULL) {
cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n");
return 0;
}
for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++)
pos += scnprintf(buf + pos, buflen - pos, "%d%s",
virtual_limit[i], i < cpr_vreg->num_corners ? " " : "");
cpr_info(cpr_vreg, "%s override voltage: [%s] uV\n", label, buf);
kfree(buf);
return rc;
use_fuse_corner_limits:
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
virtual_limit[i] = fuse_limit[cpr_vreg->corner_map[i]];
return rc;
}
/*
* This function loads per-virtual-corner ceiling and floor voltages from device
* tree if their respective device tree properties are present. These limits
* override those found in the per-fuse-corner arrays fuse_ceiling_volt and
* fuse_floor_volt.
*/
static int cpr_init_ceiling_floor_override_voltages(
struct cpr_regulator *cpr_vreg, struct device *dev)
{
int rc, i;
int size = cpr_vreg->num_corners + 1;
cpr_vreg->ceiling_volt = devm_kzalloc(dev, sizeof(int) * size,
GFP_KERNEL);
cpr_vreg->floor_volt = devm_kzalloc(dev, sizeof(int) * size,
GFP_KERNEL);
cpr_vreg->cpr_max_ceiling = devm_kzalloc(dev, sizeof(int) * size,
GFP_KERNEL);
if (!cpr_vreg->ceiling_volt || !cpr_vreg->floor_volt ||
!cpr_vreg->cpr_max_ceiling)
return -ENOMEM;
rc = cpr_fill_override_voltage(cpr_vreg, dev,
"qcom,cpr-voltage-ceiling-override", "ceiling",
cpr_vreg->ceiling_volt, cpr_vreg->fuse_ceiling_volt);
if (rc)
return rc;
rc = cpr_fill_override_voltage(cpr_vreg, dev,
"qcom,cpr-voltage-floor-override", "floor",
cpr_vreg->floor_volt, cpr_vreg->fuse_floor_volt);
if (rc)
return rc;
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
if (cpr_vreg->floor_volt[i] > cpr_vreg->ceiling_volt[i]) {
cpr_err(cpr_vreg, "virtual corner %d floor=%d uV > ceiling=%d uV\n",
i, cpr_vreg->floor_volt[i],
cpr_vreg->ceiling_volt[i]);
return -EINVAL;
}
if (cpr_vreg->ceiling_max < cpr_vreg->ceiling_volt[i])
cpr_vreg->ceiling_max = cpr_vreg->ceiling_volt[i];
cpr_vreg->cpr_max_ceiling[i] = cpr_vreg->ceiling_volt[i];
}
return rc;
}
/*
* This function computes the per-virtual-corner floor voltages from
* per-virtual-corner ceiling voltages with an offset specified by a
* device-tree property. This must be called after open-loop voltage
* scaling, floor_volt array loading and the ceiling voltage is
* conditionally reduced to the open-loop voltage. It selects the
* maximum value between the calculated floor voltage values and
* the floor_volt array values and stores them in the floor_volt array.
*/
static int cpr_init_floor_to_ceiling_range(
struct cpr_regulator *cpr_vreg, struct device *dev)
{
int rc, i, tuple_count, tuple_match, len, pos;
u32 index, floor_volt_adjust = 0;
char *prop_str, *buf;
size_t buflen;
prop_str = "qcom,cpr-floor-to-ceiling-max-range";
if (!of_find_property(dev->of_node, prop_str, &len))
return 0;
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/*
* No matching index to use for floor-to-ceiling
* max range.
*/
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 (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_str, len);
return -EINVAL;
}
for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
index = tuple_match * cpr_vreg->num_corners
+ i - CPR_CORNER_MIN;
rc = of_property_read_u32_index(dev->of_node, prop_str,
index, &floor_volt_adjust);
if (rc) {
cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
prop_str, index, rc);
return rc;
}
if ((int)floor_volt_adjust >= 0) {
cpr_vreg->floor_volt[i] = max(cpr_vreg->floor_volt[i],
(cpr_vreg->ceiling_volt[i]
- (int)floor_volt_adjust));
cpr_vreg->floor_volt[i]
= DIV_ROUND_UP(cpr_vreg->floor_volt[i],
cpr_vreg->step_volt) *
cpr_vreg->step_volt;
if (cpr_vreg->open_loop_volt[i]
< cpr_vreg->floor_volt[i])
cpr_vreg->open_loop_volt[i]
= cpr_vreg->floor_volt[i];
}
}
/*
* Log per-virtual-corner voltage limits resulted after considering the
* floor-to-ceiling max range since they are useful for baseline CPR
* debugging.
*/
buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf);
buf = kzalloc(buflen, GFP_KERNEL);
if (buf == NULL) {
cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n");
return 0;
}
for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++)
pos += scnprintf(buf + pos, buflen - pos, "%d%s",
cpr_vreg->floor_volt[i],
i < cpr_vreg->num_corners ? " " : "");
cpr_info(cpr_vreg, "Final floor override voltages: [%s] uV\n", buf);
kfree(buf);
return 0;
}
static int cpr_init_step_quotient(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int len = 0;
u32 step_quot[CPR_NUM_RING_OSC];
int i, rc;
if (!of_find_property(of_node, "qcom,cpr-step-quotient", &len)) {
cpr_err(cpr_vreg, "qcom,cpr-step-quotient property missing\n");
return -EINVAL;
}
if (len == sizeof(u32)) {
/* Single step quotient used for all ring oscillators. */
rc = of_property_read_u32(of_node, "qcom,cpr-step-quotient",
step_quot);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n",
rc);
return rc;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++)
cpr_vreg->step_quotient[i] = step_quot[0];
} else if (len == sizeof(u32) * CPR_NUM_RING_OSC) {
/* Unique step quotient used per ring oscillator. */
rc = of_property_read_u32_array(of_node,
"qcom,cpr-step-quotient", step_quot, CPR_NUM_RING_OSC);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n",
rc);
return rc;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++)
cpr_vreg->step_quotient[i]
= step_quot[cpr_vreg->cpr_fuse_ro_sel[i]];
} else {
cpr_err(cpr_vreg, "qcom,cpr-step-quotient has invalid length=%d\n",
len);
return -EINVAL;
}
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
cpr_debug(cpr_vreg, "step_quotient[%d]=%u\n", i,
cpr_vreg->step_quotient[i]);
return 0;
}
static int cpr_init_cpr_parameters(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
int rc = 0;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-ref-clk",
&cpr_vreg->ref_clk_khz, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-delay",
&cpr_vreg->timer_delay_us, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-up",
&cpr_vreg->timer_cons_up, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-down",
&cpr_vreg->timer_cons_down, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-irq-line",
&cpr_vreg->irq_line, rc);
if (rc)
return rc;
rc = cpr_init_step_quotient(pdev, cpr_vreg);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-up-threshold",
&cpr_vreg->up_threshold, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-down-threshold",
&cpr_vreg->down_threshold, rc);
if (rc)
return rc;
cpr_info(cpr_vreg, "up threshold = %u, down threshold = %u\n",
cpr_vreg->up_threshold, cpr_vreg->down_threshold);
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-idle-clocks",
&cpr_vreg->idle_clocks, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-gcnt-time",
&cpr_vreg->gcnt_time_us, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-up-limit",
&cpr_vreg->vdd_apc_step_up_limit, rc);
if (rc)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-down-limit",
&cpr_vreg->vdd_apc_step_down_limit, rc);
if (rc)
return rc;
rc = of_property_read_u32(of_node, "qcom,cpr-clamp-timer-interval",
&cpr_vreg->clamp_timer_interval);
if (rc && rc != -EINVAL) {
cpr_err(cpr_vreg,
"error reading qcom,cpr-clamp-timer-interval, rc=%d\n",
rc);
return rc;
}
cpr_vreg->clamp_timer_interval = min(cpr_vreg->clamp_timer_interval,
(u32)RBIF_TIMER_ADJ_CLAMP_INT_MASK);
/* Init module parameter with the DT value */
cpr_vreg->enable = of_property_read_bool(of_node, "qcom,cpr-enable");
cpr_info(cpr_vreg, "CPR is %s by default.\n",
cpr_vreg->enable ? "enabled" : "disabled");
return 0;
}
static void cpr_regulator_switch_adj_cpus(struct cpr_regulator *cpr_vreg)
{
cpr_vreg->last_volt = cpr_vreg->adj_cpus_last_volt
[cpr_vreg->online_cpus];
cpr_vreg->save_ctl = cpr_vreg->adj_cpus_save_ctl[cpr_vreg->online_cpus];
cpr_vreg->save_irq = cpr_vreg->adj_cpus_save_irq[cpr_vreg->online_cpus];
if (cpr_vreg->adj_cpus_quot_adjust)
cpr_vreg->quot_adjust = cpr_vreg->adj_cpus_quot_adjust
[cpr_vreg->online_cpus];
if (cpr_vreg->adj_cpus_open_loop_volt)
cpr_vreg->open_loop_volt
= cpr_vreg->adj_cpus_open_loop_volt
[cpr_vreg->online_cpus];
if (cpr_vreg->adj_cpus_open_loop_volt_as_ceiling)
cpr_vreg->ceiling_volt = cpr_vreg->open_loop_volt;
}
static void cpr_regulator_set_online_cpus(struct cpr_regulator *cpr_vreg)
{
int i, j;
cpr_vreg->online_cpus = 0;
get_online_cpus();
for_each_online_cpu(i)
for (j = 0; j < cpr_vreg->num_adj_cpus; j++)
if (i == cpr_vreg->adj_cpus[j])
cpr_vreg->online_cpus++;
put_online_cpus();
}
static int cpr_regulator_cpu_callback(struct notifier_block *nb,
unsigned long action, void *data)
{
struct cpr_regulator *cpr_vreg = container_of(nb, struct cpr_regulator,
cpu_notifier);
int cpu = (long)data;
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
int prev_online_cpus, rc, i;
action &= ~CPU_TASKS_FROZEN;
if (action != CPU_UP_PREPARE && action != CPU_UP_CANCELED
&& action != CPU_DEAD)
return NOTIFY_OK;
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
mutex_lock(&cpr_vreg->cpr_mutex);
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
if (cpr_vreg->skip_voltage_change_during_suspend
&& cpr_vreg->is_cpr_suspended) {
/* Do nothing during system suspend/resume */
goto done;
}
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
prev_online_cpus = cpr_vreg->online_cpus;
cpr_regulator_set_online_cpus(cpr_vreg);
if (action == CPU_UP_PREPARE)
for (i = 0; i < cpr_vreg->num_adj_cpus; i++)
if (cpu == cpr_vreg->adj_cpus[i]) {
cpr_vreg->online_cpus++;
break;
}
if (cpr_vreg->online_cpus == prev_online_cpus)
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
goto done;
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
cpr_debug(cpr_vreg, "adjusting corner %d quotient for %d cpus\n",
cpr_vreg->corner, cpr_vreg->online_cpus);
cpr_regulator_switch_adj_cpus(cpr_vreg);
if (cpr_vreg->corner) {
rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
cpr_vreg->corner, true);
if (rc)
cpr_err(cpr_vreg, "could not update quotient, rc=%d\n",
rc);
}
regulator: cpr-regulator: fix race condition in CPU hotplug callback Fix a race condition which occurs when setting the voltage corner of a cpr-regulator device from both the regulator framework and the CPU hotplug paths simultaneously. This issue was introduced by this change: 48ba32f0eb1a452e2e6f3d7477bdf5288e32b43d - "regulator: cpr-regulator: add online CPUs based voltage adjustment support" This race condition can cause crashes due to undervolting on boards which define per-online-core target quotient or initial voltage adjustments when the following events occur: 0. Initial state: regulator at corner X (i.e. cpr_vreg->corner = X) 1. cpr_regulator_set_voltage() called from the regulator framework path for corner Y where Y > X 2. cpr_regulator_cpu_callback() is executed and reads cpr_vreg->corner finding its value equal to X 3. cpr_regulator_cpu_callback() calls cpr_regulator_set_voltage() to set corner X (this is blocked waiting on the mutex) 4. cpr_regulator_set_voltage() call from the regulator framework path finishes setting corner Y and releases the mutex 5. cpr_regulator_set_voltage() call from hotplug path begins switching to corner X 6. cpr_regulator_set_voltage() call from hotplug path finishes switching to corner X 7. The voltage for corner X is insufficient for the processor frequency which requested corner Y leading to a crash (likely from an L2 cache bit flip) Correct this race condition by taking the mutex lock before accessing any cpr_vreg struct members in cpr_regulator_cpu_callback(). Change-Id: I019f0246f068be8eeabe8b24dc5e7bd67ef5d2dd Signed-off-by: David Collins <collinsd@codeaurora.org>
2015-05-13 20:54:38 +00:00
done:
mutex_unlock(&cpr_vreg->cpr_mutex);
return NOTIFY_OK;
}
static int cpr_parse_adj_cpus_init_voltage(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int rc, i, j, k, tuple_count, tuple_match, len, offset;
int *temp;
if (!of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
NULL))
return 0;
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for voltage adjustment. */
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;
}
len = (cpr_vreg->num_adj_cpus + 1) * tuple_count
* cpr_vreg->num_corners;
temp = kzalloc(sizeof(int) * len, GFP_KERNEL);
if (!temp) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
return -ENOMEM;
}
cpr_vreg->adj_cpus_open_loop_volt = devm_kzalloc(dev,
sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_open_loop_volt) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
rc = -ENOMEM;
goto done;
}
cpr_vreg->adj_cpus_open_loop_volt[0] = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
* (cpr_vreg->num_corners + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_open_loop_volt[0]) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
rc = -ENOMEM;
goto done;
}
for (i = 1; i <= cpr_vreg->num_adj_cpus; i++)
cpr_vreg->adj_cpus_open_loop_volt[i] =
cpr_vreg->adj_cpus_open_loop_volt[0] +
i * (cpr_vreg->num_corners + 1);
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
temp, len);
if (rc) {
cpr_err(cpr_vreg, "failed to read qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment, rc=%d\n",
rc);
goto done;
}
cpr_debug(cpr_vreg, "Open loop voltage based on number of online CPUs:\n");
offset = tuple_match * cpr_vreg->num_corners *
(cpr_vreg->num_adj_cpus + 1);
for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
k = j - 1 + offset;
cpr_vreg->adj_cpus_open_loop_volt[i][j]
= cpr_vreg->open_loop_volt[j] + temp[k];
cpr_vreg->adj_cpus_open_loop_volt[i][j]
= DIV_ROUND_UP(cpr_vreg->
adj_cpus_open_loop_volt[i][j],
cpr_vreg->step_volt) * cpr_vreg->step_volt;
if (cpr_vreg->adj_cpus_open_loop_volt[i][j]
> cpr_vreg->ceiling_volt[j])
cpr_vreg->adj_cpus_open_loop_volt[i][j]
= cpr_vreg->ceiling_volt[j];
if (cpr_vreg->adj_cpus_open_loop_volt[i][j]
< cpr_vreg->floor_volt[j])
cpr_vreg->adj_cpus_open_loop_volt[i][j]
= cpr_vreg->floor_volt[j];
cpr_debug(cpr_vreg, "cpus=%d, corner=%d, volt=%d\n",
i, j, cpr_vreg->adj_cpus_open_loop_volt[i][j]);
}
offset += cpr_vreg->num_corners;
}
cpr_vreg->adj_cpus_open_loop_volt_as_ceiling
= of_property_read_bool(dev->of_node,
"qcom,cpr-online-cpu-init-voltage-as-ceiling");
done:
kfree(temp);
return rc;
}
static int cpr_parse_adj_cpus_target_quot(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int rc, i, j, k, tuple_count, tuple_match, len, offset;
int *temp;
if (!of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
NULL))
return 0;
if (cpr_vreg->cpr_fuse_map_count) {
if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
/* No matching index to use for quotient adjustment. */
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;
}
len = (cpr_vreg->num_adj_cpus + 1) * tuple_count
* cpr_vreg->num_corners;
temp = kzalloc(sizeof(int) * len, GFP_KERNEL);
if (!temp) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
return -ENOMEM;
}
cpr_vreg->adj_cpus_quot_adjust = devm_kzalloc(dev,
sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_quot_adjust) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
rc = -ENOMEM;
goto done;
}
cpr_vreg->adj_cpus_quot_adjust[0] = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
* (cpr_vreg->num_corners + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_quot_adjust[0]) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
rc = -ENOMEM;
goto done;
}
for (i = 1; i <= cpr_vreg->num_adj_cpus; i++)
cpr_vreg->adj_cpus_quot_adjust[i] =
cpr_vreg->adj_cpus_quot_adjust[0] +
i * (cpr_vreg->num_corners + 1);
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
temp, len);
if (rc) {
cpr_err(cpr_vreg, "failed to read qcom,cpr-online-cpu-virtual-corner-quotient-adjustment, rc=%d\n",
rc);
goto done;
}
cpr_debug(cpr_vreg, "Target quotients based on number of online CPUs:\n");
offset = tuple_match * cpr_vreg->num_corners *
(cpr_vreg->num_adj_cpus + 1);
for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
k = j - 1 + offset;
cpr_vreg->adj_cpus_quot_adjust[i][j] =
cpr_vreg->quot_adjust[j] - temp[k];
cpr_debug(cpr_vreg, "cpus=%d, corner=%d, quot=%d\n",
i, j,
cpr_vreg->cpr_fuse_target_quot[
cpr_vreg->corner_map[j]]
- cpr_vreg->adj_cpus_quot_adjust[i][j]);
}
offset += cpr_vreg->num_corners;
}
done:
kfree(temp);
return rc;
}
static int cpr_init_per_cpu_adjustments(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int rc, i, j;
if (!of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
NULL)
&& !of_find_property(dev->of_node,
"qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
NULL)) {
/* 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;
}
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",
rc);
return rc;
}
rc = cpr_parse_adj_cpus_target_quot(cpr_vreg, dev);
if (rc) {
cpr_err(cpr_vreg, "cpr_parse_adj_cpus_target_quot failed: rc =%d\n",
rc);
return rc;
}
cpr_vreg->adj_cpus_last_volt = devm_kzalloc(dev,
sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
GFP_KERNEL);
cpr_vreg->adj_cpus_save_ctl = devm_kzalloc(dev,
sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
GFP_KERNEL);
cpr_vreg->adj_cpus_save_irq = devm_kzalloc(dev,
sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_last_volt || !cpr_vreg->adj_cpus_save_ctl ||
!cpr_vreg->adj_cpus_save_irq) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
return -ENOMEM;
}
cpr_vreg->adj_cpus_last_volt[0] = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
* (cpr_vreg->num_corners + 1),
GFP_KERNEL);
cpr_vreg->adj_cpus_save_ctl[0] = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
* (cpr_vreg->num_corners + 1),
GFP_KERNEL);
cpr_vreg->adj_cpus_save_irq[0] = devm_kzalloc(dev,
sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
* (cpr_vreg->num_corners + 1),
GFP_KERNEL);
if (!cpr_vreg->adj_cpus_last_volt[0] ||
!cpr_vreg->adj_cpus_save_ctl[0] ||
!cpr_vreg->adj_cpus_save_irq[0]) {
cpr_err(cpr_vreg, "Could not allocate memory\n");
return -ENOMEM;
}
for (i = 1; i <= cpr_vreg->num_adj_cpus; i++) {
j = i * (cpr_vreg->num_corners + 1);
cpr_vreg->adj_cpus_last_volt[i] =
cpr_vreg->adj_cpus_last_volt[0] + j;
cpr_vreg->adj_cpus_save_ctl[i] =
cpr_vreg->adj_cpus_save_ctl[0] + j;
cpr_vreg->adj_cpus_save_irq[i] =
cpr_vreg->adj_cpus_save_irq[0] + j;
}
for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
cpr_vreg->adj_cpus_save_ctl[i][j] =
cpr_vreg->save_ctl[j];
cpr_vreg->adj_cpus_save_irq[i][j] =
cpr_vreg->save_irq[j];
cpr_vreg->adj_cpus_last_volt[i][j]
= cpr_vreg->adj_cpus_open_loop_volt
? cpr_vreg->adj_cpus_open_loop_volt[i][j]
: cpr_vreg->open_loop_volt[j];
}
}
cpr_regulator_set_online_cpus(cpr_vreg);
cpr_debug(cpr_vreg, "%d cpus online\n", cpr_vreg->online_cpus);
devm_kfree(dev, cpr_vreg->last_volt);
devm_kfree(dev, cpr_vreg->save_ctl);
devm_kfree(dev, cpr_vreg->save_irq);
if (cpr_vreg->adj_cpus_quot_adjust)
devm_kfree(dev, cpr_vreg->quot_adjust);
if (cpr_vreg->adj_cpus_open_loop_volt)
devm_kfree(dev, cpr_vreg->open_loop_volt);
if (cpr_vreg->adj_cpus_open_loop_volt_as_ceiling)
devm_kfree(dev, cpr_vreg->ceiling_volt);
cpr_regulator_switch_adj_cpus(cpr_vreg);
cpr_vreg->skip_voltage_change_during_suspend
= of_property_read_bool(dev->of_node,
"qcom,cpr-skip-voltage-change-during-suspend");
cpr_vreg->cpu_notifier.notifier_call = cpr_regulator_cpu_callback;
register_hotcpu_notifier(&cpr_vreg->cpu_notifier);
return rc;
}
static int cpr_rpm_apc_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
int rc, len = 0;
struct device_node *of_node = pdev->dev.of_node;
if (!of_find_property(of_node, "rpm-apc-supply", NULL))
return 0;
cpr_vreg->rpm_apc_vreg = devm_regulator_get(&pdev->dev, "rpm-apc");
if (IS_ERR_OR_NULL(cpr_vreg->rpm_apc_vreg)) {
rc = PTR_RET(cpr_vreg->rpm_apc_vreg);
if (rc != -EPROBE_DEFER)
cpr_err(cpr_vreg, "devm_regulator_get: rpm-apc: rc=%d\n",
rc);
return rc;
}
if (!of_find_property(of_node, "qcom,rpm-apc-corner-map", &len)) {
cpr_err(cpr_vreg,
"qcom,rpm-apc-corner-map missing:\n");
return -EINVAL;
}
if (len != cpr_vreg->num_corners * sizeof(u32)) {
cpr_err(cpr_vreg,
"qcom,rpm-apc-corner-map length=%d is invalid: required:%d\n",
len, cpr_vreg->num_corners);
return -EINVAL;
}
cpr_vreg->rpm_apc_corner_map = devm_kzalloc(&pdev->dev,
(cpr_vreg->num_corners + 1) *
sizeof(*cpr_vreg->rpm_apc_corner_map), GFP_KERNEL);
if (!cpr_vreg->rpm_apc_corner_map) {
cpr_err(cpr_vreg, "Can't allocate memory for cpr_vreg->rpm_apc_corner_map\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,rpm-apc-corner-map",
&cpr_vreg->rpm_apc_corner_map[1], cpr_vreg->num_corners);
if (rc)
cpr_err(cpr_vreg, "read qcom,rpm-apc-corner-map failed, rc = %d\n",
rc);
return rc;
}
static int cpr_vsens_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
int rc = 0, len = 0;
struct device_node *of_node = pdev->dev.of_node;
if (of_find_property(of_node, "vdd-vsens-voltage-supply", NULL)) {
cpr_vreg->vdd_vsens_voltage = devm_regulator_get(&pdev->dev,
"vdd-vsens-voltage");
if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_voltage)) {
rc = PTR_ERR(cpr_vreg->vdd_vsens_voltage);
cpr_vreg->vdd_vsens_voltage = NULL;
if (rc == -EPROBE_DEFER)
return rc;
/* device not found */
cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-voltage: rc=%d\n",
rc);
return 0;
}
}
if (of_find_property(of_node, "vdd-vsens-corner-supply", NULL)) {
cpr_vreg->vdd_vsens_corner = devm_regulator_get(&pdev->dev,
"vdd-vsens-corner");
if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_corner)) {
rc = PTR_ERR(cpr_vreg->vdd_vsens_corner);
cpr_vreg->vdd_vsens_corner = NULL;
if (rc == -EPROBE_DEFER)
return rc;
/* device not found */
cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-corner: rc=%d\n",
rc);
return 0;
}
if (!of_find_property(of_node, "qcom,vsens-corner-map", &len)) {
cpr_err(cpr_vreg, "qcom,vsens-corner-map missing\n");
return -EINVAL;
}
if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) {
cpr_err(cpr_vreg, "qcom,vsens-corner-map length=%d is invalid: required:%d\n",
len, cpr_vreg->num_fuse_corners);
return -EINVAL;
}
cpr_vreg->vsens_corner_map = devm_kcalloc(&pdev->dev,
(cpr_vreg->num_fuse_corners + 1),
sizeof(*cpr_vreg->vsens_corner_map), GFP_KERNEL);
if (!cpr_vreg->vsens_corner_map)
return -ENOMEM;
rc = of_property_read_u32_array(of_node,
"qcom,vsens-corner-map",
&cpr_vreg->vsens_corner_map[1],
cpr_vreg->num_fuse_corners);
if (rc)
cpr_err(cpr_vreg, "read qcom,vsens-corner-map failed, rc = %d\n",
rc);
}
return rc;
}
static int cpr_disable_on_temp(struct cpr_regulator *cpr_vreg, bool disable)
{
int rc = 0;
mutex_lock(&cpr_vreg->cpr_mutex);
if (cpr_vreg->cpr_fuse_disable ||
(cpr_vreg->cpr_thermal_disable == disable))
goto out;
cpr_vreg->cpr_thermal_disable = disable;
if (cpr_vreg->enable && cpr_vreg->corner) {
if (disable) {
cpr_debug(cpr_vreg, "Disabling CPR - below temperature threshold [%d]\n",
cpr_vreg->cpr_disable_temp_threshold);
/* disable CPR and force open-loop */
cpr_ctl_disable(cpr_vreg);
rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
cpr_vreg->corner, false);
if (rc < 0)
cpr_err(cpr_vreg, "Failed to set voltage, rc=%d\n",
rc);
} else {
/* enable CPR */
cpr_debug(cpr_vreg, "Enabling CPR - above temperature thresold [%d]\n",
cpr_vreg->cpr_enable_temp_threshold);
rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
cpr_vreg->corner, true);
if (rc < 0)
cpr_err(cpr_vreg, "Failed to set voltage, rc=%d\n",
rc);
}
}
out:
mutex_unlock(&cpr_vreg->cpr_mutex);
return rc;
}
static void tsens_threshold_notify(struct therm_threshold *tsens_cb_data)
{
struct threshold_info *info = tsens_cb_data->parent;
struct cpr_regulator *cpr_vreg = container_of(info,
struct cpr_regulator, tsens_threshold_config);
int rc = 0;
cpr_debug(cpr_vreg, "Triggered tsens-notification trip_type=%d for thermal_zone_id=%d\n",
tsens_cb_data->trip_triggered, tsens_cb_data->sensor_id);
switch (tsens_cb_data->trip_triggered) {
case THERMAL_TRIP_CONFIGURABLE_HI:
rc = cpr_disable_on_temp(cpr_vreg, false);
if (rc < 0)
cpr_err(cpr_vreg, "Failed to enable CPR, rc=%d\n", rc);
break;
case THERMAL_TRIP_CONFIGURABLE_LOW:
rc = cpr_disable_on_temp(cpr_vreg, true);
if (rc < 0)
cpr_err(cpr_vreg, "Failed to disable CPR, rc=%d\n", rc);
break;
default:
cpr_debug(cpr_vreg, "trip-type %d not supported\n",
tsens_cb_data->trip_triggered);
break;
}
if (tsens_cb_data->cur_state != tsens_cb_data->trip_triggered) {
rc = sensor_mgr_set_threshold(tsens_cb_data->sensor_id,
tsens_cb_data->threshold);
if (rc < 0)
cpr_err(cpr_vreg,
"Failed to set temp. threshold, rc=%d\n", rc);
else
tsens_cb_data->cur_state =
tsens_cb_data->trip_triggered;
}
}
static int cpr_check_tsens(struct cpr_regulator *cpr_vreg)
{
int rc = 0;
struct tsens_device tsens_dev;
unsigned long temp = 0;
bool disable;
if (tsens_is_ready() > 0) {
tsens_dev.sensor_num = cpr_vreg->tsens_id;
rc = tsens_get_temp(&tsens_dev, &temp);
if (rc < 0) {
cpr_err(cpr_vreg, "Faled to read tsens, rc=%d\n", rc);
return rc;
}
disable = (int) temp <= cpr_vreg->cpr_disable_temp_threshold;
rc = cpr_disable_on_temp(cpr_vreg, disable);
if (rc)
cpr_err(cpr_vreg, "Failed to %s CPR, rc=%d\n",
disable ? "disable" : "enable", rc);
}
return rc;
}
static int cpr_thermal_init(struct cpr_regulator *cpr_vreg)
{
int rc;
struct device_node *of_node = cpr_vreg->dev->of_node;
if (!of_find_property(of_node, "qcom,cpr-thermal-sensor-id", NULL))
return 0;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-thermal-sensor-id",
&cpr_vreg->tsens_id, rc);
if (rc < 0)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-disable-temp-threshold",
&cpr_vreg->cpr_disable_temp_threshold, rc);
if (rc < 0)
return rc;
CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-enable-temp-threshold",
&cpr_vreg->cpr_enable_temp_threshold, rc);
if (rc < 0)
return rc;
if (cpr_vreg->cpr_disable_temp_threshold >=
cpr_vreg->cpr_enable_temp_threshold) {
cpr_err(cpr_vreg, "Invalid temperature threshold cpr_disable_temp[%d] >= cpr_enable_temp[%d]\n",
cpr_vreg->cpr_disable_temp_threshold,
cpr_vreg->cpr_enable_temp_threshold);
return -EINVAL;
}
cpr_vreg->cpr_disable_on_temperature = true;
return 0;
}
static int cpr_init_cpr(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct resource *res;
int rc = 0;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr_clk");
if (res && res->start)
cpr_vreg->rbcpr_clk_addr = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr");
if (!res || !res->start) {
cpr_err(cpr_vreg, "missing rbcpr address: res=%p\n", res);
return -EINVAL;
}
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)
return rc;
/*
* Limit open loop voltages based upon per corner ceiling and floor
* voltages.
*/
rc = cpr_limit_open_loop_voltage(cpr_vreg);
if (rc)
return rc;
/*
* Fill the OPP table for this device with virtual voltage corner to
* open-loop voltage pairs.
*/
rc = cpr_populate_opp_table(cpr_vreg, &pdev->dev);
if (rc)
return rc;
/* Reduce the ceiling voltage if allowed. */
rc = cpr_reduce_ceiling_voltage(cpr_vreg, &pdev->dev);
if (rc)
return rc;
/* Load CPR floor to ceiling range if exist. */
rc = cpr_init_floor_to_ceiling_range(cpr_vreg, &pdev->dev);
if (rc)
return rc;
/* Init all voltage set points of APC regulator for CPR */
rc = cpr_init_cpr_voltages(cpr_vreg, &pdev->dev);
if (rc)
return rc;
/* Get and Init interrupt */
cpr_vreg->cpr_irq = platform_get_irq(pdev, 0);
if (!cpr_vreg->cpr_irq) {
cpr_err(cpr_vreg, "missing CPR IRQ\n");
return -EINVAL;
}
/* Configure CPR HW but keep it disabled */
rc = cpr_config(cpr_vreg, &pdev->dev);
if (rc)
return rc;
rc = request_threaded_irq(cpr_vreg->cpr_irq, NULL, cpr_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_RISING, "cpr",
cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "CPR: request irq failed for IRQ %d\n",
cpr_vreg->cpr_irq);
return rc;
}
return 0;
}
/*
* Create a set of virtual fuse rows if optional device tree properties are
* present.
*/
static int cpr_remap_efuse_data(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
struct property *prop;
u64 fuse_param;
u32 *temp;
int size, rc, i, bits, in_row, in_bit, out_row, out_bit;
prop = of_find_property(of_node, "qcom,fuse-remap-source", NULL);
if (!prop) {
/* No fuse remapping needed. */
return 0;
}
size = prop->length / sizeof(u32);
if (size == 0 || size % 4) {
cpr_err(cpr_vreg, "qcom,fuse-remap-source has invalid size=%d\n",
size);
return -EINVAL;
}
size /= 4;
rc = of_property_read_u32(of_node, "qcom,fuse-remap-base-row",
&cpr_vreg->remapped_row_base);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,fuse-remap-base-row, rc=%d\n",
rc);
return rc;
}
temp = kzalloc(sizeof(*temp) * size * 4, GFP_KERNEL);
if (!temp) {
cpr_err(cpr_vreg, "temp memory allocation failed\n");
return -ENOMEM;
}
rc = of_property_read_u32_array(of_node, "qcom,fuse-remap-source", temp,
size * 4);
if (rc) {
cpr_err(cpr_vreg, "could not read qcom,fuse-remap-source, rc=%d\n",
rc);
goto done;
}
/*
* Format of tuples in qcom,fuse-remap-source property:
* <row bit-offset bit-count fuse-read-method>
*/
for (i = 0, bits = 0; i < size; i++)
bits += temp[i * 4 + 2];
cpr_vreg->num_remapped_rows = DIV_ROUND_UP(bits, 64);
cpr_vreg->remapped_row = devm_kzalloc(&pdev->dev,
sizeof(*cpr_vreg->remapped_row) * cpr_vreg->num_remapped_rows,
GFP_KERNEL);
if (!cpr_vreg->remapped_row) {
cpr_err(cpr_vreg, "remapped_row memory allocation failed\n");
rc = -ENOMEM;
goto done;
}
for (i = 0, out_row = 0, out_bit = 0; i < size; i++) {
in_row = temp[i * 4];
in_bit = temp[i * 4 + 1];
bits = temp[i * 4 + 2];
while (bits > 64) {
fuse_param = cpr_read_efuse_param(cpr_vreg, in_row,
in_bit, 64, temp[i * 4 + 3]);
cpr_vreg->remapped_row[out_row++]
|= fuse_param << out_bit;
if (out_bit > 0)
cpr_vreg->remapped_row[out_row]
|= fuse_param >> (64 - out_bit);
bits -= 64;
in_bit += 64;
}
fuse_param = cpr_read_efuse_param(cpr_vreg, in_row, in_bit,
bits, temp[i * 4 + 3]);
cpr_vreg->remapped_row[out_row] |= fuse_param << out_bit;
if (bits < 64 - out_bit) {
out_bit += bits;
} else {
out_row++;
if (out_bit > 0)
cpr_vreg->remapped_row[out_row]
|= fuse_param >> (64 - out_bit);
out_bit = bits - (64 - out_bit);
}
}
done:
kfree(temp);
return rc;
}
static int cpr_efuse_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct resource *res;
int len;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr");
if (!res || !res->start) {
cpr_err(cpr_vreg, "efuse_addr missing: res=%p\n", res);
return -EINVAL;
}
cpr_vreg->efuse_addr = res->start;
len = res->end - res->start + 1;
cpr_info(cpr_vreg, "efuse_addr = %pa (len=0x%x)\n", &res->start, len);
cpr_vreg->efuse_base = ioremap(cpr_vreg->efuse_addr, len);
if (!cpr_vreg->efuse_base) {
cpr_err(cpr_vreg, "Unable to map efuse_addr %pa\n",
&cpr_vreg->efuse_addr);
return -EINVAL;
}
return 0;
}
static void cpr_efuse_free(struct cpr_regulator *cpr_vreg)
{
iounmap(cpr_vreg->efuse_base);
}
static void cpr_parse_cond_min_volt_fuse(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
int rc;
u32 fuse_sel[5];
/*
* Restrict all pvs corner voltages to a minimum value of
* qcom,cpr-cond-min-voltage if the fuse defined in
* qcom,cpr-fuse-cond-min-volt-sel does not read back with
* the expected value.
*/
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-cond-min-volt-sel", fuse_sel, 5);
if (!rc) {
if (!cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel))
cpr_vreg->flags |= FLAGS_SET_MIN_VOLTAGE;
}
}
static void cpr_parse_speed_bin_fuse(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
int rc;
u64 fuse_bits;
u32 fuse_sel[4];
u32 speed_bits;
rc = of_property_read_u32_array(of_node,
"qcom,speed-bin-fuse-sel", fuse_sel, 4);
if (!rc) {
fuse_bits = cpr_read_efuse_row(cpr_vreg,
fuse_sel[0], fuse_sel[3]);
speed_bits = (fuse_bits >> fuse_sel[1]) &
((1 << fuse_sel[2]) - 1);
cpr_info(cpr_vreg, "[row: %d]: 0x%llx, speed_bits = %d\n",
fuse_sel[0], fuse_bits, speed_bits);
cpr_vreg->speed_bin = speed_bits;
} else {
cpr_vreg->speed_bin = SPEED_BIN_NONE;
}
}
static int cpr_voltage_uplift_enable_check(struct cpr_regulator *cpr_vreg,
struct device_node *of_node)
{
int rc;
u32 fuse_sel[5];
u32 uplift_speed_bin;
rc = of_property_read_u32_array(of_node,
"qcom,cpr-fuse-uplift-sel", fuse_sel, 5);
if (!rc) {
rc = of_property_read_u32(of_node,
"qcom,cpr-uplift-speed-bin",
&uplift_speed_bin);
if (rc < 0) {
cpr_err(cpr_vreg,
"qcom,cpr-uplift-speed-bin missing\n");
return rc;
}
if (cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel)
&& (uplift_speed_bin == cpr_vreg->speed_bin)
&& !(cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE)) {
cpr_vreg->flags |= FLAGS_UPLIFT_QUOT_VOLT;
}
}
return 0;
}
/*
* Read in the number of fuse corners and then allocate memory for arrays that
* are sized based upon the number of fuse corners.
*/
static int cpr_fuse_corner_array_alloc(struct device *dev,
struct cpr_regulator *cpr_vreg)
{
int rc;
size_t len;
rc = of_property_read_u32(dev->of_node, "qcom,cpr-fuse-corners",
&cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "qcom,cpr-fuse-corners missing: rc=%d\n", rc);
return rc;
}
if (cpr_vreg->num_fuse_corners < CPR_FUSE_CORNER_MIN
|| cpr_vreg->num_fuse_corners > CPR_FUSE_CORNER_LIMIT) {
cpr_err(cpr_vreg, "corner count=%d is invalid\n",
cpr_vreg->num_fuse_corners);
return -EINVAL;
}
/*
* The arrays sized based on the fuse corner count ignore element 0
* in order to simplify indexing throughout the driver since min_uV = 0
* cannot be passed into a set_voltage() callback.
*/
len = cpr_vreg->num_fuse_corners + 1;
cpr_vreg->pvs_corner_v = devm_kzalloc(dev,
len * sizeof(*cpr_vreg->pvs_corner_v), GFP_KERNEL);
cpr_vreg->cpr_fuse_target_quot = devm_kzalloc(dev,
len * sizeof(*cpr_vreg->cpr_fuse_target_quot), GFP_KERNEL);
cpr_vreg->cpr_fuse_ro_sel = devm_kzalloc(dev,
len * sizeof(*cpr_vreg->cpr_fuse_ro_sel), GFP_KERNEL);
cpr_vreg->fuse_ceiling_volt = devm_kzalloc(dev,
len * (sizeof(*cpr_vreg->fuse_ceiling_volt)), GFP_KERNEL);
cpr_vreg->fuse_floor_volt = devm_kzalloc(dev,
len * (sizeof(*cpr_vreg->fuse_floor_volt)), GFP_KERNEL);
cpr_vreg->step_quotient = devm_kzalloc(dev,
len * sizeof(*cpr_vreg->step_quotient), GFP_KERNEL);
if (cpr_vreg->pvs_corner_v == NULL || cpr_vreg->cpr_fuse_ro_sel == NULL
|| cpr_vreg->fuse_ceiling_volt == NULL
|| cpr_vreg->fuse_floor_volt == NULL
|| cpr_vreg->cpr_fuse_target_quot == NULL
|| cpr_vreg->step_quotient == NULL) {
cpr_err(cpr_vreg, "Could not allocate memory for CPR arrays\n");
return -ENOMEM;
}
return 0;
}
static int cpr_voltage_plan_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
struct device_node *of_node = pdev->dev.of_node;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
int rc, i;
u32 min_uv = 0;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-ceiling",
&cpr_vreg->fuse_ceiling_volt[CPR_FUSE_CORNER_MIN],
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr-voltage-ceiling missing: rc=%d\n", rc);
return rc;
}
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-floor",
&cpr_vreg->fuse_floor_volt[CPR_FUSE_CORNER_MIN],
cpr_vreg->num_fuse_corners);
if (rc < 0) {
cpr_err(cpr_vreg, "cpr-voltage-floor missing: rc=%d\n", rc);
return rc;
}
cpr_parse_cond_min_volt_fuse(cpr_vreg, of_node);
rc = cpr_voltage_uplift_enable_check(cpr_vreg, of_node);
if (rc < 0) {
cpr_err(cpr_vreg, "voltage uplift enable check failed, %d\n",
rc);
return rc;
}
if (cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE) {
of_property_read_u32(of_node, "qcom,cpr-cond-min-voltage",
&min_uv);
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
i++)
if (cpr_vreg->fuse_ceiling_volt[i] < min_uv) {
cpr_vreg->fuse_ceiling_volt[i] = min_uv;
cpr_vreg->fuse_floor_volt[i] = min_uv;
} else if (cpr_vreg->fuse_floor_volt[i] < min_uv) {
cpr_vreg->fuse_floor_volt[i] = min_uv;
msm: cpr-regulator: modify how initial voltages and limits are specified Currently, the initial voltage as well as the CPR ceiling and floor voltages for each voltage corner are defined using these four device tree properties: qcom,pvs-init-voltage, qcom,pvs-corner-ceiling-slow, qcom,pvs-corner-ceiling-nom, and qcom,pvs-corner-ceiling-fast. Unfortunately, since qcom,pvs-init-voltage only defines the initial voltage for the turbo voltage corner for each PVS bin value, the initial voltage used for the SVS and nominal corners must be calculated using a complicated and potentially error prone algorithm using the four properties mentioned earlier. Furthermore, the coarse slow/nom/fast binning leads to wasted power for parts with intermediate PVS bin defined initial voltage requirements when the CPR controller is disabled. Therefore, replace the four properties listed above with the following three properties: qcom,pvs-voltage-table, qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor. The property qcom,pvs-voltage-table specifies a mapping from a PVS bin value to the initial voltage for each voltage corner. The properties qcom,cpr-voltage-ceiling and qcom,cpr-voltage-floor specify the ceiling and floor for each voltage corner respectively. Also, remove all usage of 'process' from inside of the cpr-regulator driver since it has no meaning without the qcom,pvs-corner-ceiling-slow/nom/fast properties. CRs-Fixed: 606213 Change-Id: I7996074b39e1f80f6dc10e25428e09d7cb364192 Signed-off-by: Ke Liu <keliu@codeaurora.org>
2014-02-04 19:53:38 +00:00
}
}
return 0;
}
static int cpr_mem_acc_init(struct platform_device *pdev,
struct cpr_regulator *cpr_vreg)
{
int rc, size;
struct property *prop;
char *corner_map_str;
if (of_find_property(pdev->dev.of_node, "mem-acc-supply", NULL)) {
cpr_vreg->mem_acc_vreg = devm_regulator_get(&pdev->dev,
"mem-acc");
if (IS_ERR_OR_NULL(cpr_vreg->mem_acc_vreg)) {
rc = PTR_RET(cpr_vreg->mem_acc_vreg);
if (rc != -EPROBE_DEFER)
cpr_err(cpr_vreg,
"devm_regulator_get: mem-acc: rc=%d\n",
rc);
return rc;
}
}
corner_map_str = "qcom,mem-acc-corner-map";
prop = of_find_property(pdev->dev.of_node, corner_map_str, NULL);
if (!prop) {
corner_map_str = "qcom,cpr-corner-map";
prop = of_find_property(pdev->dev.of_node, corner_map_str,
NULL);
if (!prop) {
cpr_err(cpr_vreg, "qcom,cpr-corner-map missing\n");
return -EINVAL;
}
}
size = prop->length / sizeof(u32);
cpr_vreg->mem_acc_corner_map = devm_kzalloc(&pdev->dev,
sizeof(int) * (size + 1),
GFP_KERNEL);
rc = of_property_read_u32_array(pdev->dev.of_node, corner_map_str,
&cpr_vreg->mem_acc_corner_map[CPR_FUSE_CORNER_MIN],
size);
if (rc) {
cpr_err(cpr_vreg, "%s missing, rc = %d\n", corner_map_str, rc);
return rc;
}
return 0;
}
#if defined(CONFIG_DEBUG_FS)
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static int cpr_enable_set(void *data, u64 val)
{
struct cpr_regulator *cpr_vreg = data;
bool old_cpr_enable;
mutex_lock(&cpr_vreg->cpr_mutex);
old_cpr_enable = cpr_vreg->enable;
cpr_vreg->enable = val;
if (old_cpr_enable == cpr_vreg->enable)
goto _exit;
if (cpr_vreg->enable && cpr_vreg->cpr_fuse_disable) {
cpr_info(cpr_vreg,
"CPR permanently disabled due to fuse values\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_vreg->enable = false;
goto _exit;
}
cpr_debug(cpr_vreg, "%s CPR [corner=%d, fuse_corner=%d]\n",
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_vreg->enable ? "enabling" : "disabling",
cpr_vreg->corner, cpr_vreg->corner_map[cpr_vreg->corner]);
if (cpr_vreg->corner) {
if (cpr_vreg->enable) {
cpr_ctl_disable(cpr_vreg);
cpr_irq_clr(cpr_vreg);
cpr_corner_restore(cpr_vreg, cpr_vreg->corner);
cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
} else {
cpr_ctl_disable(cpr_vreg);
cpr_irq_set(cpr_vreg, 0);
}
}
_exit:
mutex_unlock(&cpr_vreg->cpr_mutex);
return 0;
}
static int cpr_enable_get(void *data, u64 *val)
{
struct cpr_regulator *cpr_vreg = data;
*val = cpr_vreg->enable;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(cpr_enable_fops, cpr_enable_get, cpr_enable_set,
"%llu\n");
static int cpr_get_cpr_ceiling(void *data, u64 *val)
{
struct cpr_regulator *cpr_vreg = data;
*val = cpr_vreg->ceiling_volt[cpr_vreg->corner];
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(cpr_ceiling_fops, cpr_get_cpr_ceiling, NULL,
"%llu\n");
static int cpr_get_cpr_floor(void *data, u64 *val)
{
struct cpr_regulator *cpr_vreg = data;
*val = cpr_vreg->floor_volt[cpr_vreg->corner];
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(cpr_floor_fops, cpr_get_cpr_floor, NULL,
"%llu\n");
static int cpr_get_cpr_max_ceiling(void *data, u64 *val)
{
struct cpr_regulator *cpr_vreg = data;
*val = cpr_vreg->cpr_max_ceiling[cpr_vreg->corner];
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(cpr_max_ceiling_fops, cpr_get_cpr_max_ceiling, NULL,
"%llu\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static int cpr_debug_info_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t cpr_debug_info_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
struct cpr_regulator *cpr_vreg = file->private_data;
char *debugfs_buf;
ssize_t len, ret = 0;
u32 gcnt, ro_sel, ctl, irq_status, reg, error_steps;
u32 step_dn, step_up, error, error_lt0, busy;
int fuse_corner;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!debugfs_buf)
return -ENOMEM;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
mutex_lock(&cpr_vreg->cpr_mutex);
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
fuse_corner = cpr_vreg->corner_map[cpr_vreg->corner];
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
"corner = %d, current_volt = %d uV\n",
cpr_vreg->corner, cpr_vreg->last_volt[cpr_vreg->corner]);
ret += len;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"fuse_corner = %d, current_volt = %d uV\n",
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
fuse_corner, cpr_vreg->last_volt[cpr_vreg->corner]);
ret += len;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner];
gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel));
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"rbcpr_gcnt_target (%u) = 0x%02X\n", ro_sel, gcnt);
ret += len;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL);
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"rbcpr_ctl = 0x%02X\n", ctl);
ret += len;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
irq_status = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"rbcpr_irq_status = 0x%02X\n", irq_status);
ret += len;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
reg = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"rbcpr_result_0 = 0x%02X\n", reg);
ret += len;
step_dn = reg & 0x01;
step_up = (reg >> RBCPR_RESULT0_STEP_UP_SHIFT) & 0x01;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
" [step_dn = %u", step_dn);
ret += len;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
", step_up = %u", step_up);
ret += len;
error_steps = (reg >> RBCPR_RESULT0_ERROR_STEPS_SHIFT)
& RBCPR_RESULT0_ERROR_STEPS_MASK;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
", error_steps = %u", error_steps);
ret += len;
error = (reg >> RBCPR_RESULT0_ERROR_SHIFT) & RBCPR_RESULT0_ERROR_MASK;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
", error = %u", error);
ret += len;
error_lt0 = (reg >> RBCPR_RESULT0_ERROR_LT0_SHIFT) & 0x01;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
", error_lt_0 = %u", error_lt0);
ret += len;
busy = (reg >> RBCPR_RESULT0_BUSY_SHIFT) & 0x01;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
", busy = %u]\n", busy);
ret += len;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
mutex_unlock(&cpr_vreg->cpr_mutex);
ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
kfree(debugfs_buf);
return ret;
}
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static const struct file_operations cpr_debug_info_fops = {
.open = cpr_debug_info_open,
.read = cpr_debug_info_read,
};
static int cpr_aging_debug_info_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t cpr_aging_debug_info_read(struct file *file, char __user *buff,
size_t count, loff_t *ppos)
{
struct cpr_regulator *cpr_vreg = file->private_data;
struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
char *debugfs_buf;
ssize_t len, ret = 0;
int i;
debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!debugfs_buf)
return -ENOMEM;
mutex_lock(&cpr_vreg->cpr_mutex);
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"aging_adj_volt = [");
ret += len;
for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
" %d", aging_info->voltage_adjust[i]);
ret += len;
}
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
" ]uV\n");
ret += len;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"aging_measurement_done = %s\n",
aging_info->cpr_aging_done ? "true" : "false");
ret += len;
len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
"aging_measurement_error = %s\n",
aging_info->cpr_aging_error ? "true" : "false");
ret += len;
mutex_unlock(&cpr_vreg->cpr_mutex);
ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
kfree(debugfs_buf);
return ret;
}
static const struct file_operations cpr_aging_debug_info_fops = {
.open = cpr_aging_debug_info_open,
.read = cpr_aging_debug_info_read,
};
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
{
struct dentry *temp;
if (IS_ERR_OR_NULL(cpr_debugfs_base)) {
cpr_err(cpr_vreg, "Could not create debugfs nodes since base directory is missing\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
return;
}
cpr_vreg->debugfs = debugfs_create_dir(cpr_vreg->rdesc.name,
cpr_debugfs_base);
if (IS_ERR_OR_NULL(cpr_vreg->debugfs)) {
cpr_err(cpr_vreg, "debugfs directory creation failed\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
return;
}
temp = debugfs_create_file("debug_info", S_IRUGO, cpr_vreg->debugfs,
cpr_vreg, &cpr_debug_info_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "debug_info node creation failed\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
return;
}
temp = debugfs_create_file("cpr_enable", S_IRUGO | S_IWUSR,
cpr_vreg->debugfs, cpr_vreg, &cpr_enable_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "cpr_enable node creation failed\n");
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
return;
}
temp = debugfs_create_file("cpr_ceiling", S_IRUGO,
cpr_vreg->debugfs, cpr_vreg, &cpr_ceiling_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "cpr_ceiling node creation failed\n");
return;
}
temp = debugfs_create_file("cpr_floor", S_IRUGO,
cpr_vreg->debugfs, cpr_vreg, &cpr_floor_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "cpr_floor node creation failed\n");
return;
}
temp = debugfs_create_file("cpr_max_ceiling", S_IRUGO,
cpr_vreg->debugfs, cpr_vreg, &cpr_max_ceiling_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "cpr_max_ceiling node creation failed\n");
return;
}
if (cpr_vreg->aging_info) {
temp = debugfs_create_file("aging_debug_info", S_IRUGO,
cpr_vreg->debugfs, cpr_vreg,
&cpr_aging_debug_info_fops);
if (IS_ERR_OR_NULL(temp)) {
cpr_err(cpr_vreg, "aging_debug_info node creation failed\n");
return;
}
}
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
}
static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg)
{
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
debugfs_remove_recursive(cpr_vreg->debugfs);
}
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static void cpr_debugfs_base_init(void)
{
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_debugfs_base = debugfs_create_dir("cpr-regulator", NULL);
if (IS_ERR_OR_NULL(cpr_debugfs_base))
pr_err("cpr-regulator debugfs base directory creation failed\n");
}
static void cpr_debugfs_base_remove(void)
{
debugfs_remove_recursive(cpr_debugfs_base);
}
#else
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
{}
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg)
{}
static void cpr_debugfs_base_init(void)
{}
static void cpr_debugfs_base_remove(void)
{}
#endif
static int cpr_regulator_probe(struct platform_device *pdev)
{
struct regulator_config reg_config = {};
struct cpr_regulator *cpr_vreg;
struct regulator_desc *rdesc;
struct device *dev = &pdev->dev;
struct regulator_init_data *init_data = pdev->dev.platform_data;
int rc;
if (!pdev->dev.of_node) {
dev_err(dev, "Device tree node is missing\n");
return -EINVAL;
}
init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node);
if (!init_data) {
dev_err(dev, "regulator init data is missing\n");
return -EINVAL;
} else {
init_data->constraints.input_uV
= init_data->constraints.max_uV;
init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
}
cpr_vreg = devm_kzalloc(&pdev->dev, sizeof(struct cpr_regulator),
GFP_KERNEL);
if (!cpr_vreg) {
dev_err(dev, "Can't allocate cpr_regulator memory\n");
return -ENOMEM;
}
cpr_vreg->dev = &pdev->dev;
cpr_vreg->rdesc.name = init_data->constraints.name;
if (cpr_vreg->rdesc.name == NULL) {
dev_err(dev, "regulator-name missing\n");
return -EINVAL;
}
rc = cpr_fuse_corner_array_alloc(&pdev->dev, cpr_vreg);
if (rc)
return rc;
rc = cpr_mem_acc_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "mem_acc intialization error rc=%d\n", rc);
return rc;
}
rc = cpr_efuse_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Wrong eFuse address specified: rc=%d\n", rc);
return rc;
}
rc = cpr_remap_efuse_data(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Could not remap fuse data: rc=%d\n", rc);
return rc;
}
rc = cpr_check_redundant(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Could not check redundant fuse: rc=%d\n",
rc);
goto err_out;
}
rc = cpr_read_fuse_revision(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Could not read fuse revision: rc=%d\n", rc);
goto err_out;
}
cpr_parse_speed_bin_fuse(cpr_vreg, dev->of_node);
cpr_parse_pvs_version_fuse(cpr_vreg, dev->of_node);
rc = cpr_read_ro_select(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Could not read RO select: rc=%d\n", rc);
goto err_out;
}
rc = cpr_find_fuse_map_match(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Could not determine fuse mapping match: rc=%d\n",
rc);
goto err_out;
}
rc = cpr_voltage_plan_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Wrong DT parameter specified: rc=%d\n", rc);
goto err_out;
}
rc = cpr_pvs_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Initialize PVS wrong: rc=%d\n", rc);
goto err_out;
}
rc = cpr_vsens_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Initialize vsens configuration failed rc=%d\n",
rc);
return rc;
}
rc = cpr_apc_init(pdev, cpr_vreg);
if (rc) {
if (rc != -EPROBE_DEFER)
cpr_err(cpr_vreg, "Initialize APC wrong: rc=%d\n", rc);
goto err_out;
}
rc = cpr_init_cpr(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Initialize CPR failed: rc=%d\n", rc);
goto err_out;
}
rc = cpr_rpm_apc_init(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Initialize RPM APC regulator failed rc=%d\n",
rc);
return rc;
}
rc = cpr_thermal_init(cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "Thermal intialization failed rc=%d\n", rc);
return rc;
}
/* Load per-online CPU adjustment data */
rc = cpr_init_per_cpu_adjustments(cpr_vreg, &pdev->dev);
if (rc) {
cpr_err(cpr_vreg, "cpr_init_per_cpu_adjustments failed: rc=%d\n",
rc);
goto err_out;
}
/* Parse dependency parameters */
if (cpr_vreg->vdd_mx) {
rc = cpr_parse_vdd_mx_parameters(pdev, cpr_vreg);
if (rc) {
cpr_err(cpr_vreg, "parsing vdd_mx parameters failed: rc=%d\n",
rc);
goto err_out;
}
}
cpr_efuse_free(cpr_vreg);
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
/*
* Ensure that enable state accurately reflects the case in which CPR
* is permanently disabled.
*/
cpr_vreg->enable &= !cpr_vreg->cpr_fuse_disable;
mutex_init(&cpr_vreg->cpr_mutex);
rdesc = &cpr_vreg->rdesc;
rdesc->owner = THIS_MODULE;
rdesc->type = REGULATOR_VOLTAGE;
rdesc->ops = &cpr_corner_ops;
reg_config.dev = &pdev->dev;
reg_config.init_data = init_data;
reg_config.driver_data = cpr_vreg;
reg_config.of_node = pdev->dev.of_node;
cpr_vreg->rdev = regulator_register(rdesc, &reg_config);
if (IS_ERR(cpr_vreg->rdev)) {
rc = PTR_ERR(cpr_vreg->rdev);
cpr_err(cpr_vreg, "regulator_register failed: rc=%d\n", rc);
cpr_apc_exit(cpr_vreg);
return rc;
}
platform_set_drvdata(pdev, cpr_vreg);
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_debugfs_init(cpr_vreg);
if (cpr_vreg->cpr_disable_on_temperature) {
rc = cpr_check_tsens(cpr_vreg);
if (rc < 0) {
cpr_err(cpr_vreg, "Unable to config CPR on tsens, rc=%d\n",
rc);
cpr_apc_exit(cpr_vreg);
cpr_debugfs_remove(cpr_vreg);
return rc;
}
}
mutex_lock(&cpr_regulator_list_mutex);
list_add(&cpr_vreg->list, &cpr_regulator_list);
mutex_unlock(&cpr_regulator_list_mutex);
return 0;
err_out:
cpr_efuse_free(cpr_vreg);
return rc;
}
static int cpr_regulator_remove(struct platform_device *pdev)
{
struct cpr_regulator *cpr_vreg;
cpr_vreg = platform_get_drvdata(pdev);
if (cpr_vreg) {
/* Disable CPR */
if (cpr_is_allowed(cpr_vreg)) {
cpr_ctl_disable(cpr_vreg);
cpr_irq_set(cpr_vreg, 0);
}
mutex_lock(&cpr_regulator_list_mutex);
list_del(&cpr_vreg->list);
mutex_unlock(&cpr_regulator_list_mutex);
if (cpr_vreg->cpu_notifier.notifier_call)
unregister_hotcpu_notifier(&cpr_vreg->cpu_notifier);
if (cpr_vreg->cpr_disable_on_temperature)
sensor_mgr_remove_threshold(cpr_vreg->dev,
&cpr_vreg->tsens_threshold_config);
cpr_apc_exit(cpr_vreg);
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_debugfs_remove(cpr_vreg);
regulator_unregister(cpr_vreg->rdev);
}
return 0;
}
static struct of_device_id cpr_regulator_match_table[] = {
{ .compatible = CPR_REGULATOR_DRIVER_NAME, },
{}
};
static struct platform_driver cpr_regulator_driver = {
.driver = {
.name = CPR_REGULATOR_DRIVER_NAME,
.of_match_table = cpr_regulator_match_table,
.owner = THIS_MODULE,
},
.probe = cpr_regulator_probe,
.remove = cpr_regulator_remove,
.suspend = cpr_regulator_suspend,
.resume = cpr_regulator_resume,
};
static int initialize_tsens_monitor(struct cpr_regulator *cpr_vreg)
{
int rc;
rc = cpr_check_tsens(cpr_vreg);
if (rc < 0) {
cpr_err(cpr_vreg, "Unable to check tsens, rc=%d\n", rc);
return rc;
}
rc = sensor_mgr_init_threshold(cpr_vreg->dev,
&cpr_vreg->tsens_threshold_config,
cpr_vreg->tsens_id,
cpr_vreg->cpr_enable_temp_threshold, /* high */
cpr_vreg->cpr_disable_temp_threshold, /* low */
tsens_threshold_notify);
if (rc < 0) {
cpr_err(cpr_vreg, "Failed to init tsens monitor, rc=%d\n", rc);
return rc;
}
rc = sensor_mgr_convert_id_and_set_threshold(
&cpr_vreg->tsens_threshold_config);
if (rc < 0)
cpr_err(cpr_vreg, "Failed to set tsens threshold, rc=%d\n",
rc);
return rc;
}
int __init cpr_regulator_late_init(void)
{
int rc;
struct cpr_regulator *cpr_vreg;
mutex_lock(&cpr_regulator_list_mutex);
list_for_each_entry(cpr_vreg, &cpr_regulator_list, list) {
if (cpr_vreg->cpr_disable_on_temperature) {
rc = initialize_tsens_monitor(cpr_vreg);
if (rc)
cpr_err(cpr_vreg, "Failed to initialize temperature monitor, rc=%d\n",
rc);
}
}
mutex_unlock(&cpr_regulator_list_mutex);
return 0;
}
late_initcall(cpr_regulator_late_init);
/**
* cpr_regulator_init() - register cpr-regulator driver
*
* This initialization function should be called in systems in which driver
* registration ordering must be controlled precisely.
*/
int __init cpr_regulator_init(void)
{
static bool initialized;
if (initialized)
return 0;
else
initialized = true;
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_debugfs_base_init();
return platform_driver_register(&cpr_regulator_driver);
}
EXPORT_SYMBOL(cpr_regulator_init);
static void __exit cpr_regulator_exit(void)
{
platform_driver_unregister(&cpr_regulator_driver);
regulator: cpr-regulator: add debugfs support for multiple devices The cpr_enable module parameter is currently tied to a global pointer, the_cpr, which corresponds to the last cpr-regulator device probed. This means that it is not possible to enable or disable the CPR controller for the cpr-regulator devices that probed first. Remove the the_cpr pointer and the cpr_enable module parameter. Replace them with a debugfs node for each cpr-regulator device which can be used to enable and disable CPR individually. Also fix the callback for the existing debug_info debugfs node since it depends upon the_cpr. New Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/kernel/debug/regulator/<regulator-name>/cpr_enable Integer which controls whether or not CPR is enabled for a particular cpr-regulator device. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for a particular cpr-regulator device. <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Old Debug Interface: /sys/module/cpr_regulator/parameters/debug_enable Integer in which bit 0 enables kernel log messages generated when handling CPR interrupts and bit 1 enables other cpr-regulator log messages. This parameter affects all cpr-regulator devices. /sys/module/cpr_regulator/parameters/cpr_enable Integer which controls whether or not CPR is enabled for the last cpr-regulator device probed. A value of 0 corresponds to CPR disabled and a value of 1 corresponds to CPR enabled. This parameter incorrectly does not control the CPR enable state for the first cpr-regulator devices probed. /sys/kernel/debug/regulator/<regulator-name>/debug_info CPR status information for the last cpr-regulator device probed (regardless of which cpr-regulator device it seems to be associated with). <regulator-name> corresponds to the name of the cpr-regulator device which is specified via the regulator-name device tree property. Change-Id: I404ebdb3180f7dd9ae095867f7545d274be29e62 Signed-off-by: David Collins <collinsd@codeaurora.org>
2014-05-23 21:39:51 +00:00
cpr_debugfs_base_remove();
}
MODULE_DESCRIPTION("CPR regulator driver");
MODULE_LICENSE("GPL v2");
arch_initcall(cpr_regulator_init);
module_exit(cpr_regulator_exit);