summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOsvaldo Banuelos <osvaldob@codeaurora.org>2016-01-29 13:46:22 -0800
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:23:13 -0700
commit0514464b8eae8636f6c775cf975011e029ac3c09 (patch)
treee4fcd5823f1d57e89e196c6845c27c609a1f64e1
parent86afb76cfdb674ee1b235dbf7151aeb104248a29 (diff)
regulator: cpr3-regulator: add support for CPRh-compliant controllers
Augment the cpr3-regulator driver to support controllers with full hardware CPR operation also known as CPR hardening. Also, introduce the cprh-kbss-regulator driver to handle CPU subsystem specific power requirements of the msmcobalt chip. Change-Id: Icac84f9533fa1895ca2466a3793ddaa8b7a4c89c CRs-Fixed: 967275 Signed-off-by: Osvaldo Banuelos <osvaldob@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt411
-rw-r--r--drivers/regulator/Kconfig11
-rw-r--r--drivers/regulator/Makefile1
-rw-r--r--drivers/regulator/cpr3-regulator.c538
-rw-r--r--drivers/regulator/cpr3-regulator.h73
-rw-r--r--drivers/regulator/cpr3-util.c352
-rw-r--r--drivers/regulator/cpr4-apss-regulator.c192
-rw-r--r--drivers/regulator/cprh-kbss-regulator.c1485
8 files changed, 2830 insertions, 233 deletions
diff --git a/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt b/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt
new file mode 100644
index 000000000000..e0f6ea2371b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt
@@ -0,0 +1,411 @@
+Qualcomm Technologies, Inc. CPRh Regulator - KBSS Specific Bindings
+
+KBSS CPRh controllers each support one CPR thread that monitors the voltage
+of a single Kryo-B CPU subystem (KBSS) cluster that is powered by a single
+regulator supply. The DCVSh block interacts with the CPRh controller for full
+hardware DCVS support.
+
+Both CPR open-loop voltages and CPR target quotients are stored in hardware
+fuses for KBSS CPRh controllers.
+
+This document describes the KBSS specific CPRh bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPRh regulators must be described in three levels of devices nodes. The first
+level describes the CPRh controller. The second level describes one hardware
+thread managed by the controller. The third level describes one regulator
+handled by the CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cprh-kbss-regulator devices.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+KBSS specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: "qcom,cprh-msmcobalt-kbss-regulator".
+
+- qcom,cpr-controller-id
+ Usage: required
+ Value type: <u32>
+ Definition: Identifies the controller number for subsystems that are managed
+ by multiple CPR controllers. For KBSS, the supported values are 0
+ and 1, corresponding to each cluster.
+
+- qcom,apm-threshold-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the APM threshold voltage in microvolts. If the
+ VDD_APCC supply voltage is above this level, then the APM is
+ switched to use VDD_APCC. If VDD_APCC is below this level,
+ then the APM is switched to use VDD_MX.
+
+- qcom,apm-hysteresis-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the voltage delta in microvolts between the APM
+ threshold voltage and the highest corner open-loop voltage
+ which may be used as the ceiling for the corner. If this
+ property is not specified, then a value of 0 is assumed.
+
+- qcom,voltage-base
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the voltage in microvolts used by the CRR controller
+ to resolve open-loop and floor voltages. In particular, this
+ voltage is added to the programmed open-loop and floor voltages
+ and it corresponds to the minimum supported setpoint of the
+ vdd-supply.
+
+- qcom,cpr-saw-use-unit-mV
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the unit used in SAW PVC
+ interface is mV. Use this for vdd-supply regulators which
+ do not use PMIC voltage control register LSBs per actually
+ unique PMIC regulator output voltage.
+
+- qcom,cpr-up-down-delay-time
+ Usage: required
+ Value type: <u32>
+ Definition: The time to delay in nanoseconds between consecutive CPR
+ measurements when the last measurement recommended
+ increasing or decreasing the vdd-supply voltage.
+
+- qcom,cpr-down-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: CPRh hardware closed-loop down error step limit which
+ defines the maximum number of vdd-supply regulator steps
+ that the voltage may be reduced as the result of a single
+ CPR measurement.
+
+- qcom,cpr-up-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: CPRh hardware closed-loop up error step limit which defines
+ the maximum number of vdd-supply regulator steps that the
+ voltage may be increased as the result of a single
+ CPR measurement.
+
+- qcom,cpr-voltage-settling-time
+ Usage: optional
+ Value type: <u32>
+ The time in nanoseconds that it takes for the vdd-supply
+ voltage to settle after being increased or decreased by
+ qcom,voltage-step microvolts. This is used as the wait
+ time after applying SDELTA voltage margin adjustments.
+
+- qcom,cpr-corner-switch-delay-time
+ Usage: optional
+ Value type: <u32>
+ The time in nanoseconds that the CPR controller must delay
+ to allow voltage settling after a corner change.
+
+- qcom,cpr-hw-closed-loop
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the KBSS CPRh controller
+ should operate in hardware closed-loop mode as opposed to
+ open-loop.
+
+- qcom,cpr-temp-point-map
+ Usage: required if qcom,corner-band-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <prop-encoded-array>
+ Definition: The temperature points in decidegrees Celsius which indicate
+ the range of temperature bands supported. If t1, t2, and t3
+ are the temperature points, then the temperature bands are:
+ (-inf, t1], (t1, t2], (t2, t3], and (t3, inf). A maximum of
+ three temperature points can be specified to define a total
+ of four different temperature bands.
+
+- qcom,cpr-initial-temp-band
+ Usage: required if qcom,corner-band-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <u32>
+ Definition: The initial temp band considering 0-based index at which
+ the baseline target quotients are derived and fused.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+KBSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+KBSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 4
+ for KBSS. These fuse corners are: LowSVS, SVS, Nominal,
+ and Turbo.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For KBSS the maximum
+ supported value is 8. These combos correspond to CPR
+ revision fuse values 0 to 7 in order.
+
+- qcom,allow-quotient-interpolation
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ interpolated CPR target quotient values. These values are
+ interpolated between the target quotient Fmax fuse values.
+
+- qcom,cpr-corner-bands
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ or qcom,corner-band-allow-temp-adjustment is specified
+ for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines how many corner bands
+ exist for each fuse combination. Supported values are 1 to 4.
+ The list must contain either qcom,cpr-fuse-combos number of
+ elements in which case the corner band counts are applied to
+ fuse combinations 1-to-1 or the list must contain exactly 1
+ element which is used regardless of the fuse combination
+ found on a given chip.
+
+- qcom,cpr-speed-bin-corner-bands
+ Usage: required if qcom,cpr-speed-bins and
+ qcom,corner-band-allow-core-count-adjustment or
+ qcom,corner-band-allow-temp-adjustment are specified for
+ this CPR3 regulator.
+ Value type: <prop-encded-array>
+ Definition: A list of integers which defines how many corner bands
+ are to be used for each speed bin. The list must contain
+ qcom,cpr-speed-bins number of elements.
+
+- qcom,corner-band-allow-core-count-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR core
+ count adjustment feature enable state for each corner band
+ in order from lowest to highest. Each element in the tuple
+ should be either 0 (per-core-count adjustment not allowed)
+ or 1 (per-core-count adjustment allowed). A maximum of four
+ corner bands may be used to partition the corner space into
+ contiguous corner ranges. For all corners within a corner band,
+ the same per-core-count adjustments are applied.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,max-core-count
+ Usage: required if qcom,corner-band-allow-core-count-adjustment is
+ specified for this CPR3 regulator.
+ Value type: <u32>
+ Definition: The maximum number of cores considered for core-count vmin
+ adjustments specified for this regulator's corner bands.
+
+- qcom,corner-band-allow-temp-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the temperature
+ adjustment feature enable state for each corner band
+ in order from lowest to highest. Each element in the tuple
+ should be either 0 (temperature adjustment not allowed)
+ or 1 (temperature adjustment allowed). A maximum of four
+ corner bands may be used to partition the corner space into
+ contiguous corner ranges. For all corners within a corner band,
+ the same temperature adjustments are applied.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,cpr-corner-band-map
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ or qcom,corner-band-allow-temp-adjustment is specified
+ for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which correspond to corner numbers
+ and define the corner bands to be used for temperature or
+ per-core-count adjustments. The corner numbers must be specified
+ in increasing order to result in partitioning the corner space
+ into contiguous corner ranges. The supported tuple size is 1
+ to 4 elements. For example, a tuple with corners defined as
+ c1, c2, c3, c4 results in the following corner band mapping:
+
+ [c1, c2) -> Corner Band 1
+ [c2, c3) -> Corner Band 2
+ [c3, c4) -> Corner Band 3
+ [c4, inf)-> Corner Band 4
+
+ Corners less than c1 will have no per-core-count or temperature
+ adjustments. Adjustments associated with each corner band X are
+ defined in the corresponding
+ qcom,cpr-corner-bandX-temp-core-voltage-adjustment property.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,cpr-corner-bandX-temp-core-voltage-adjustment
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ is specified for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists for corner bandX. The possible
+ values for X are 1 to 4. Each tuple defines the temperature based
+ voltage adjustment in microvolts for each temperature band
+ from lowest to highest for a given number of online cores. Each
+ tuple must have a number of elements equal to either (the number
+ of elements in qcom,cpr-temp-point-map + 1), if
+ qcom,cpr-temp-point-map is specified, or 1.
+
+ Each tuple list must contain a number of tuples equal to
+ either qcom,max-core-count, if qcom,max-core-count is
+ specified, or 1. The tuples should be ordered from lowest
+ to highest core count.
+
+ The tuple list grouping must contain qcom,cpr-fuse-combos
+ number of tuple lists in which case the lists are matched to
+ fuse combinations 1-to-1 or qcom,cpr-speed-bins number of
+ tuple lists in which case the lists are matched to
+ speed bins 1-to-1 or exactly 1 list which is used regardless
+ of the fuse combination and speed bin found on a given chip.
+
+=======
+Example
+=======
+
+apc0_cpr: cprh-ctrl@179c8000 {
+ compatible = "qcom,cprh-msmcobalt-kbss-regulator";
+ reg = <0x179c8000 0x4000>, <0x00784000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ clocks = <&clock_gcc clk_gcc_hmss_rbcpr_clk>;
+ clock-names = "core_clk";
+ qcom,cpr-ctrl-name = "apc0";
+ qcom,cpr-controller-id = <0>;
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-up-down-delay-time = <3000>;
+ qcom,cpr-step-quot-init-min = <11>;
+ qcom,cpr-step-quot-init-max = <13>;
+ qcom,cpr-count-mode = <2>; /* Staggered */
+ qcom,cpr-down-error-step-limit = <1>;
+ qcom,cpr-up-error-step-limit = <1>;
+ qcom,cpr-corner-switch-delay-time = <1600>;
+ qcom,cpr-voltage-settling-time = <1600>;
+
+ qcom,apm-threshold-voltage = <800000>;
+ qcom,apm-hysteresis-voltage = <4000>;
+ qcom,voltage-step = <4000>;
+ qcom,voltage-base = <352000>;
+ qcom,cpr-saw-use-unit-mV;
+
+ qcom,cpr-enable;
+ qcom,cpr-hw-closed-loop;
+
+ qcom,cpr-initial-temp-band = <3>;
+ qcom,cpr-temp-point-map = <0 25 85>;
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <2>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <0>;
+
+ apc0_pwrcl_vreg: regulator-pwrcl {
+ regulator-name = "apc0_pwrcl_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <24>;
+
+ qcom,cpr-fuse-corners = <4>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <23>;
+
+ qcom,cpr-corner-fmax-map = <7 10 17 23>;
+
+ qcom,cpr-voltage-ceiling =
+ <632000 632000 632000 632000 632000
+ 632000 632000 700000 700000 700000
+ 828000 828000 828000 828000 828000
+ 828000 828000 1024000 1024000 1024000
+ 1024000 1024000 1024000>;
+
+ qcom,cpr-voltage-floor =
+ <572000 572000 572000 572000 572000
+ 572000 572000 568000 568000 568000
+ 684000 684000 684000 684000 684000
+ 684000 684000 856000 856000 856000
+ 856000 856000 856000>;
+
+ qcom,corner-frequencies =
+ <300000000 345600000 422400000
+ 499200000 576000000 633600000
+ 710400000 806400000 883200000
+ 960000000 1036800000 1113600000
+ 1190400000 1248000000 1324800000
+ 1401600000 1478400000 1497600000
+ 1574400000 1651200000 1728000000
+ 1804800000 1881600000>;
+ };
+
+ qcom,cpr-corner-bands = <4>;
+ qcom,corner-band-allow-core-count-adjustment = <1 1 1 1>;
+ qcom,corner-band-allow-temp-adjustment = <1 0 0 0>;
+ qcom,cpr-corner-band-map = <7 14 18 20>;
+ qcom,max-core-count = <4>;
+ qcom,cpr-corner-band1-temp-core-voltage-adjustment =
+ <(-24000) (-20000) (-20000) (-16000)>,
+ <(-16000) (-16000) (-12000) (-8000)>,
+ <(-8000) (-8000) (-4000) (-4000)>,
+ <0 0 0 0>;
+ qcom,cpr-corner-band2-temp-core-voltage-adjustment =
+ <(-36000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ <(-24000) 0 0 0>,
+ < 0 0 0 0>;
+ qcom,cpr-corner-band3-temp-core-voltage-adjustment =
+ <(-40000) 0 0 0>,
+ <(-36000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ < 0 0 0 0>;
+ qcom,cpr-corner-band4-temp-core-voltage-adjustment =
+ <(-44000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ <(-24000) 0 0 0>,
+ < 0 0 0 0>;
+ };
+ };
+}
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index c92de1e940a3..67818321aaa2 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -904,6 +904,17 @@ config REGULATOR_CPR4_APSS
adjustments for performance boost mode. This driver reads both initial
voltage and CPR target quotient values out of hardware fuses.
+config REGULATOR_CPRH_KBSS
+ bool "CPRH regulator for KBSS"
+ depends on OF
+ select REGULATOR_CPR3
+ help
+ This driver supports Qualcomm Technologies, Inc. KBSS application
+ processor specific features including CPR hardening (CPRh) and two
+ CPRh controllers which monitor the two KBSS clusters each powered by
+ independent voltage supplies. This driver reads both initial voltage
+ and CPR target quotient values out of hardware fuses.
+
config REGULATOR_KRYO
bool "Kryo regulator driver"
depends on OF
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 26a7df108fb4..4dc9b4d4c5b7 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o
obj-$(CONFIG_REGULATOR_CPR3_HMSS) += cpr3-hmss-regulator.o
obj-$(CONFIG_REGULATOR_CPR3_MMSS) += cpr3-mmss-regulator.o
obj-$(CONFIG_REGULATOR_CPR4_APSS) += cpr4-apss-regulator.o
+obj-$(CONFIG_REGULATOR_CPRH_KBSS) += cprh-kbss-regulator.o
obj-$(CONFIG_REGULATOR_QPNP_LABIBB) += qpnp-labibb-regulator.o
obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o
obj-$(CONFIG_REGULATOR_KRYO) += kryo-regulator.o
diff --git a/drivers/regulator/cpr3-regulator.c b/drivers/regulator/cpr3-regulator.c
index ca09c8d78c28..5c028d51fdc0 100644
--- a/drivers/regulator/cpr3-regulator.c
+++ b/drivers/regulator/cpr3-regulator.c
@@ -227,6 +227,50 @@
#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31)
#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0)
+/* CPRh controller specific registers and bit definitions */
+#define CPRH_REG_CORNER(corner) (0x3A00 + 0x4 * (corner))
+#define CPRH_CORNER_INIT_VOLTAGE_MASK GENMASK(7, 0)
+#define CPRH_CORNER_INIT_VOLTAGE_SHIFT 0
+#define CPRH_CORNER_FLOOR_VOLTAGE_MASK GENMASK(15, 8)
+#define CPRH_CORNER_FLOOR_VOLTAGE_SHIFT 8
+#define CPRH_CORNER_QUOT_DELTA_MASK GENMASK(24, 16)
+#define CPRH_CORNER_QUOT_DELTA_SHIFT 16
+#define CPRH_CORNER_RO_SEL_MASK GENMASK(28, 25)
+#define CPRH_CORNER_RO_SEL_SHIFT 25
+#define CPRH_CORNER_CPR_CL_DISABLE BIT(29)
+#define CPRH_CORNER_CORE_TEMP_MARGIN_DISABLE BIT(30)
+#define CPRH_CORNER_LAST_KNOWN_VOLTAGE_ENABLE BIT(31)
+#define CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE 255
+#define CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE 255
+#define CPRH_CORNER_QUOT_DELTA_MAX_VALUE 511
+
+#define CPRH_REG_CTL 0x3AA0
+#define CPRH_CTL_OSM_ENABLED BIT(0)
+#define CPRH_CTL_BASE_VOLTAGE_MASK GENMASK(10, 1)
+#define CPRH_CTL_BASE_VOLTAGE_SHIFT 1
+#define CPRH_CTL_INIT_MODE_MASK GENMASK(16, 11)
+#define CPRH_CTL_INIT_MODE_SHIFT 11
+#define CPRH_CTL_MODE_SWITCH_DELAY_MASK GENMASK(24, 17)
+#define CPRH_CTL_MODE_SWITCH_DELAY_SHIFT 17
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_MASK GENMASK(28, 25)
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT 25
+#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_MASK GENMASK(31, 29)
+#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_SHIFT 29
+
+#define CPRH_REG_STATUS 0x3AA4
+#define CPRH_STATUS_CORNER GENMASK(5, 0)
+#define CPRH_STATUS_CORNER_LAST_VOLT_MASK GENMASK(17, 6)
+#define CPRH_STATUS_CORNER_LAST_VOLT_SHIFT 6
+
+#define CPRH_REG_CORNER_BAND 0x3AA8
+#define CPRH_CORNER_BAND_MASK GENMASK(5, 0)
+#define CPRH_CORNER_BAND_SHIFT 6
+#define CPRH_CORNER_BAND_MAX_COUNT 4
+
+#define CPRH_MARGIN_TEMP_CORE_VBAND(core, vband) \
+ ((vband) == 0 ? CPR4_REG_MARGIN_TEMP_CORE(core) \
+ : 0x3AB0 + 0x40 * ((vband) - 1) + 0x4 * (core))
+
/*
* The amount of time to wait for the CPR controller to become idle when
* performing an aging measurement.
@@ -260,6 +304,18 @@
*/
#define CPR3_REGISTER_WRITE_DELAY_US 200
+/*
+ * The number of times the CPRh controller multiplies the mode switch
+ * delay before utilizing it.
+ */
+#define CPRH_MODE_SWITCH_DELAY_FACTOR 4
+
+/*
+ * The number of times the CPRh controller multiplies the delta quotient
+ * steps before utilizing it.
+ */
+#define CPRH_DELTA_QUOT_STEP_FACTOR 4
+
static DEFINE_MUTEX(cpr3_controller_list_mutex);
static LIST_HEAD(cpr3_controller_list);
static struct dentry *cpr3_debugfs_base;
@@ -835,11 +891,10 @@ static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
static void cpr3_write_temp_core_margin(struct cpr3_controller *ctrl,
int addr, int *temp_core_adj)
{
- struct cpr4_sdelta *sdelta = ctrl->aggr_corner.sdelta;
int i, margin_steps;
u32 reg = 0;
- for (i = 0; i < sdelta->temp_band_count; i++) {
+ for (i = 0; i < ctrl->temp_band_count; i++) {
margin_steps = max(min(temp_core_adj[i], 127), -128);
reg |= (margin_steps & CPR4_MARGIN_TEMP_CORE_ADJ_MASK) <<
(i * CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT);
@@ -968,6 +1023,326 @@ static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
}
/**
+ * cpr3_regulator_set_base_target_quot() - configure the target quotient
+ * for each RO of the CPR3 regulator for CPRh operation.
+ * In particular, the quotient of the RO selected for operation
+ * should correspond to the lowest target quotient across the
+ * corners supported by the single regulator of the CPR3 thread.
+ * @vreg: Pointer to the CPR3 regulator
+ * @base_quots: Pointer to the base quotient array. The array must be
+ * of size CPR3_RO_COUNT and it is populated with the
+ * base quotient per-RO.
+ *
+ * Return: none
+ */
+static void cpr3_regulator_set_base_target_quot(struct cpr3_regulator *vreg,
+ u32 *base_quots)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ int i, j, ro_mask = CPR3_RO_MASK;
+ u32 min_quot;
+
+ for (i = 0; i < vreg->corner_count; i++)
+ ro_mask &= vreg->corner[i].ro_mask;
+
+ /* Unmask the ROs selected for active use. */
+ cpr3_write(ctrl, CPR3_REG_RO_MASK(vreg->thread->thread_id),
+ ro_mask);
+
+ for (i = 0; i < CPR3_RO_COUNT; i++) {
+ for (j = 0, min_quot = INT_MAX; j < vreg->corner_count; j++)
+ if (vreg->corner[j].target_quot[i])
+ min_quot = min(min_quot,
+ vreg->corner[j].target_quot[i]);
+
+ if (min_quot == INT_MAX)
+ min_quot = 0;
+
+ cpr3_write(ctrl,
+ CPR3_REG_TARGET_QUOT(vreg->thread->thread_id, i),
+ min_quot);
+
+ base_quots[i] = min_quot;
+ }
+}
+
+/**
+ * cpr3_regulator_init_cprh_corners() - configure the per-corner CPRh registers
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function programs the controller registers which contain all information
+ * necessary to resolve the closed-loop voltage per-corner at runtime such as
+ * open-loop and floor voltages, target quotient delta, and RO select value.
+ * These registers also provide a means to disable closed-loop operation, core
+ * and temperature adjustments.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_init_cprh_corners(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+ u32 reg, delta_quot_steps, ro_sel;
+ u32 *base_quots;
+ int open_loop_volt_steps, floor_volt_steps, i, j, rc = 0;
+
+ base_quots = kcalloc(CPR3_RO_COUNT, sizeof(*base_quots),
+ GFP_KERNEL);
+ if (!base_quots)
+ return -ENOMEM;
+
+ cpr3_regulator_set_base_target_quot(vreg, base_quots);
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+ for (j = 0, ro_sel = INT_MAX; j < CPR3_RO_COUNT; j++) {
+ if (corner->target_quot[j]) {
+ ro_sel = j;
+ break;
+ }
+ }
+
+ if (ro_sel == INT_MAX) {
+ cpr3_err(vreg, "corner=%d has invalid RO select value\n",
+ i);
+ rc = -EINVAL;
+ goto free_base_quots;
+ }
+
+ open_loop_volt_steps = DIV_ROUND_UP(corner->open_loop_volt -
+ ctrl->base_volt,
+ ctrl->step_volt);
+ floor_volt_steps = DIV_ROUND_UP(corner->floor_volt -
+ ctrl->base_volt,
+ ctrl->step_volt);
+ delta_quot_steps = DIV_ROUND_UP(corner->target_quot[ro_sel] -
+ base_quots[ro_sel],
+ CPRH_DELTA_QUOT_STEP_FACTOR);
+
+ if (open_loop_volt_steps > CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE ||
+ floor_volt_steps > CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE ||
+ delta_quot_steps > CPRH_CORNER_QUOT_DELTA_MAX_VALUE) {
+ cpr3_err(ctrl, "invalid CPRh corner configuration: open_loop_volt_steps=%d (%d max.), floor_volt_steps=%d (%d max), delta_quot_steps=%d (%d max)\n",
+ open_loop_volt_steps,
+ CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE,
+ floor_volt_steps,
+ CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE,
+ delta_quot_steps,
+ CPRH_CORNER_QUOT_DELTA_MAX_VALUE);
+ rc = -EINVAL;
+ goto free_base_quots;
+ }
+
+ reg = (open_loop_volt_steps << CPRH_CORNER_INIT_VOLTAGE_SHIFT)
+ & CPRH_CORNER_INIT_VOLTAGE_MASK;
+ reg |= (floor_volt_steps << CPRH_CORNER_FLOOR_VOLTAGE_SHIFT)
+ & CPRH_CORNER_FLOOR_VOLTAGE_MASK;
+ reg |= (delta_quot_steps << CPRH_CORNER_QUOT_DELTA_SHIFT)
+ & CPRH_CORNER_QUOT_DELTA_MASK;
+ reg |= (ro_sel << CPRH_CORNER_RO_SEL_SHIFT)
+ & CPRH_CORNER_RO_SEL_MASK;
+
+ if (corner->use_open_loop)
+ reg |= CPRH_CORNER_CPR_CL_DISABLE;
+
+ cpr3_debug(ctrl, "corner=%d open_loop_volt_steps=%d, floor_volt_steps=%d, delta_quot_steps=%d, base_volt=%d, step_volt=%d, base_quot=%d\n",
+ i, open_loop_volt_steps, floor_volt_steps,
+ delta_quot_steps, ctrl->base_volt,
+ ctrl->step_volt, base_quots[ro_sel]);
+ cpr3_write(ctrl, CPRH_REG_CORNER(i), reg);
+ }
+
+free_base_quots:
+ kfree(base_quots);
+ return rc;
+}
+
+/**
+ * cprh_controller_program_sdelta() - programs hardware SDELTA registers with
+ * the margins that need to be applied at different online
+ * core-count and temperature bands for each corner band. Also,
+ * programs hardware register configuration for core-count and
+ * temp-based adjustments
+ *
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * CPR interface/bus clocks must be enabled before calling this function.
+ *
+ * Return: none
+ */
+static void cprh_controller_program_sdelta(
+ struct cpr3_controller *ctrl)
+{
+ struct cpr3_regulator *vreg = &ctrl->thread[0].vreg[0];
+ struct cprh_corner_band *corner_band;
+ struct cpr4_sdelta *sdelta;
+ int i, j, index;
+ u32 reg = 0;
+
+ if (!vreg->allow_core_count_adj && !vreg->allow_temp_adj)
+ return;
+
+ cpr4_regulator_init_temp_points(ctrl);
+
+ for (i = 0; i < CPRH_CORNER_BAND_MAX_COUNT; i++) {
+ reg |= (i < vreg->corner_band_count ?
+ vreg->corner_band[i].corner
+ & CPRH_CORNER_BAND_MASK :
+ vreg->corner_count + 1)
+ << (i * CPRH_CORNER_BAND_SHIFT);
+ }
+
+ cpr3_write(ctrl, CPRH_REG_CORNER_BAND, reg);
+
+ for (i = 0; i < vreg->corner_band_count; i++) {
+ corner_band = &vreg->corner_band[i];
+ sdelta = corner_band->sdelta;
+
+ if (vreg->allow_core_count_adj)
+ cpr3_write_temp_core_margin(ctrl,
+ CPRH_MARGIN_TEMP_CORE_VBAND(0, i),
+ &sdelta->table[0]);
+
+ for (j = 0; j < sdelta->max_core_count; j++) {
+ index = j * sdelta->temp_band_count;
+
+ cpr3_write_temp_core_margin(ctrl,
+ CPRH_MARGIN_TEMP_CORE_VBAND(
+ sdelta->allow_core_count_adj
+ ? j + 1 : vreg->max_core_count, i),
+ &sdelta->table[index]);
+ }
+ }
+
+ if (!vreg->allow_core_count_adj) {
+ cpr3_masked_write(ctrl, CPR4_REG_MISC,
+ CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
+ vreg->max_core_count
+ << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
+ }
+
+ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
+ CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
+ | CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
+ | CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
+ | CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
+ | CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
+ vreg->max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
+ | ((vreg->allow_core_count_adj)
+ ? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
+ | (vreg->allow_temp_adj ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
+ | ((ctrl->use_hw_closed_loop)
+ ? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
+ | (ctrl->use_hw_closed_loop
+ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0));
+
+ /* Ensure that previous CPR register writes complete */
+ mb();
+}
+
+/**
+ * cpr3_regulator_init_cprh() - performs hardware initialization at the
+ * controller and thread level required for CPRh operation.
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * CPR interface/bus clocks must be enabled before calling this function.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_init_cprh(struct cpr3_controller *ctrl)
+{
+ u32 reg, pmic_step_size = 1;
+ u64 temp;
+ int rc;
+
+ /* Single thread, single regulator supported */
+ if (ctrl->thread_count != 1) {
+ cpr3_err(ctrl, "expected 1 thread but found %d\n",
+ ctrl->thread_count);
+ return -EINVAL;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ cprh_controller_program_sdelta(ctrl);
+
+ rc = cpr3_regulator_init_cprh_corners(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize CPRh corner registers\n");
+ return rc;
+ }
+
+ if (ctrl->saw_use_unit_mV)
+ pmic_step_size = ctrl->step_volt / 1000;
+ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
+ CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
+ (pmic_step_size
+ << CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
+
+ cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
+ CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
+ (ctrl->down_error_step_limit
+ << CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
+
+ cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
+ CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
+ (ctrl->up_error_step_limit
+ << CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
+
+ if (ctrl->voltage_settling_time) {
+ /*
+ * Configure the settling timer used to account for
+ * one VDD supply step.
+ */
+ temp = (u64)ctrl->cpr_clock_rate
+ * (u64)ctrl->voltage_settling_time;
+ do_div(temp, 1000000000);
+ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
+ CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
+ temp
+ << CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
+ }
+
+ if (ctrl->corner_switch_delay_time) {
+ /*
+ * Configure the settling timer used to delay
+ * following SAW requests
+ */
+ temp = (u64)ctrl->cpr_clock_rate
+ * (u64)ctrl->corner_switch_delay_time;
+ do_div(temp, 1000000000);
+ do_div(temp, CPRH_MODE_SWITCH_DELAY_FACTOR);
+ cpr3_masked_write(ctrl, CPRH_REG_CTL,
+ CPRH_CTL_MODE_SWITCH_DELAY_MASK,
+ temp << CPRH_CTL_MODE_SWITCH_DELAY_SHIFT);
+ }
+
+ /*
+ * Program base voltage and voltage multiplier values which
+ * are used for floor and initial voltage calculations by the
+ * CPRh controller.
+ */
+ reg = (DIV_ROUND_UP(ctrl->base_volt, ctrl->step_volt)
+ << CPRH_CTL_BASE_VOLTAGE_SHIFT)
+ & CPRH_CTL_BASE_VOLTAGE_MASK;
+ reg |= (DIV_ROUND_UP(ctrl->step_volt, 1000)
+ << CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT)
+ & CPRH_CTL_VOLTAGE_MULTIPLIER_MASK;
+ /* Enable OSM block interface with CPR */
+ reg |= CPRH_CTL_OSM_ENABLED;
+ cpr3_masked_write(ctrl, CPRH_REG_CTL, CPRH_CTL_BASE_VOLTAGE_MASK
+ | CPRH_CTL_VOLTAGE_MULTIPLIER_MASK
+ | CPRH_CTL_OSM_ENABLED, reg);
+
+ /* Enable loop_en */
+ cpr3_ctrl_loop_enable(ctrl);
+
+ return 0;
+}
+
+/**
* cpr3_regulator_init_ctrl() - performs hardware initialization of CPR
* controller registers
* @ctrl: Pointer to the CPR3 controller
@@ -1078,7 +1453,8 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
}
if (ctrl->supports_hw_closed_loop) {
- if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4 ||
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
ctrl->use_hw_closed_loop
@@ -1095,8 +1471,9 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
}
}
- if (ctrl->use_hw_closed_loop ||
- ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
+ if ((ctrl->use_hw_closed_loop ||
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
+ ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
rc = regulator_enable(ctrl->vdd_limit_regulator);
if (rc) {
cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
@@ -1118,19 +1495,34 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
rc);
return rc;
}
+ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ rc = cpr3_regulator_init_cprh(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "CPRh-specific controller initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
}
/* Ensure that all register writes complete before disabling clocks. */
wmb();
- cpr3_clock_disable(ctrl);
- ctrl->cpr_enabled = false;
+ /* Keep CPR clocks on for CPRh full HW closed-loop operation */
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ cpr3_clock_disable(ctrl);
+ ctrl->cpr_enabled = false;
+ }
if (!ctrl->cpr_allowed_sw || !ctrl->cpr_allowed_hw)
mode = "open-loop";
- else if (ctrl->supports_hw_closed_loop)
+ else if (ctrl->supports_hw_closed_loop &&
+ ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
mode = ctrl->use_hw_closed_loop
? "HW closed-loop" : "SW closed-loop";
+ else if (ctrl->supports_hw_closed_loop &&
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ mode = ctrl->use_hw_closed_loop
+ ? "full HW closed-loop" : "open-loop";
else
mode = "closed-loop";
@@ -3668,6 +4060,55 @@ static struct regulator_ops cpr3_regulator_ops = {
};
/**
+ * cprh_regulator_get_voltage() - get the voltage corner for the CPR3 regulator
+ * associated with the regulator device
+ * @rdev: Regulator device pointer for the cpr3-regulator
+ *
+ * This function is passed as a callback function into the regulator ops that
+ * are registered for each cpr3-regulator device of a CPRh controller. The
+ * corner is read directly from CPRh hardware register.
+ *
+ * Return: voltage corner value offset by CPR3_CORNER_OFFSET
+ */
+static int cprh_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ bool cpr_enabled;
+ u32 reg, rc;
+
+ mutex_lock(&ctrl->lock);
+
+ cpr_enabled = ctrl->cpr_enabled;
+ if (!cpr_enabled) {
+ rc = cpr3_clock_enable(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
+ mutex_unlock(&ctrl->lock);
+ return CPR3_REGULATOR_CORNER_INVALID;
+ }
+ ctrl->cpr_enabled = true;
+ }
+
+ reg = cpr3_read(vreg->thread->ctrl, CPRH_REG_STATUS);
+
+ if (!cpr_enabled) {
+ cpr3_clock_disable(ctrl);
+ ctrl->cpr_enabled = false;
+ }
+
+ mutex_unlock(&ctrl->lock);
+
+ return (reg & CPRH_STATUS_CORNER)
+ + CPR3_CORNER_OFFSET;
+}
+
+static struct regulator_ops cprh_regulator_ops = {
+ .get_voltage = cprh_regulator_get_voltage,
+ .list_corner_voltage = cpr3_regulator_list_corner_voltage,
+};
+
+/**
* cpr3_print_result() - print CPR measurement results to the kernel log for
* debugging purposes
* @thread: Pointer to the CPR3 thread
@@ -4043,13 +4484,18 @@ static int cpr3_regulator_vreg_register(struct cpr3_regulator *vreg)
}
init_data->constraints.input_uV = init_data->constraints.max_uV;
- init_data->constraints.valid_ops_mask
+ rdesc = &vreg->rdesc;
+ if (vreg->thread->ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ /* CPRh regulators are treated as always-on regulators */
+ rdesc->ops = &cprh_regulator_ops;
+ } else {
+ init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
+ rdesc->ops = &cpr3_regulator_ops;
+ }
- rdesc = &vreg->rdesc;
rdesc->n_voltages = vreg->corner_count;
rdesc->name = init_data->constraints.name;
- rdesc->ops = &cpr3_regulator_ops;
rdesc->owner = THIS_MODULE;
rdesc->type = REGULATOR_VOLTAGE;
@@ -5192,11 +5638,13 @@ int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
cpr3_ctrl_loop_disable(ctrl);
- rc = cpr3_closed_loop_disable(ctrl);
- if (rc)
- cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ rc = cpr3_closed_loop_disable(ctrl);
+ if (rc)
+ cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
- ctrl->cpr_suspended = true;
+ ctrl->cpr_suspended = true;
+ }
mutex_unlock(&ctrl->lock);
return 0;
@@ -5215,11 +5663,14 @@ int cpr3_regulator_resume(struct cpr3_controller *ctrl)
mutex_lock(&ctrl->lock);
- ctrl->cpr_suspended = false;
-
- rc = cpr3_regulator_update_ctrl_state(ctrl);
- if (rc)
- cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ ctrl->cpr_suspended = false;
+ rc = cpr3_regulator_update_ctrl_state(ctrl);
+ if (rc)
+ cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
+ } else {
+ cpr3_ctrl_loop_enable(ctrl);
+ }
mutex_unlock(&ctrl->lock);
return 0;
@@ -5238,7 +5689,7 @@ static int cpr3_regulator_validate_controller(struct cpr3_controller *ctrl)
struct cpr3_regulator *vreg;
int i, j, allow_boost_vreg_count = 0;
- if (!ctrl->vdd_regulator) {
+ if (!ctrl->vdd_regulator && ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
cpr3_err(ctrl, "vdd regulator missing\n");
return -EINVAL;
} else if (ctrl->sensor_count <= 0
@@ -5324,13 +5775,16 @@ int cpr3_regulator_register(struct platform_device *pdev,
}
ctrl->cpr_ctrl_base = devm_ioremap(dev, res->start, resource_size(res));
- ctrl->irq = platform_get_irq_byname(pdev, "cpr");
- if (ctrl->irq < 0) {
- cpr3_err(ctrl, "missing CPR interrupt\n");
- return ctrl->irq;
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ ctrl->irq = platform_get_irq_byname(pdev, "cpr");
+ if (ctrl->irq < 0) {
+ cpr3_err(ctrl, "missing CPR interrupt\n");
+ return ctrl->irq;
+ }
}
- if (ctrl->supports_hw_closed_loop) {
+ if (ctrl->supports_hw_closed_loop && ctrl->ctrl_type !=
+ CPR_CTRL_TYPE_CPRH) {
rc = msm_spm_probe_done();
if (rc) {
if (rc != -EPROBE_DEFER)
@@ -5345,11 +5799,13 @@ int cpr3_regulator_register(struct platform_device *pdev,
}
}
- rc = cpr3_regulator_init_ctrl_data(ctrl);
- if (rc) {
- cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
- rc);
- return rc;
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ rc = cpr3_regulator_init_ctrl_data(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
}
for (i = 0; i < ctrl->thread_count; i++) {
@@ -5389,15 +5845,21 @@ int cpr3_regulator_register(struct platform_device *pdev,
}
}
- rc = devm_request_threaded_irq(dev, ctrl->irq, NULL, cpr3_irq_handler,
- IRQF_ONESHOT | IRQF_TRIGGER_RISING, "cpr3", ctrl);
- if (rc) {
- cpr3_err(ctrl, "could not request IRQ %d, rc=%d\n",
- ctrl->irq, rc);
- goto free_regulators;
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ rc = devm_request_threaded_irq(dev, ctrl->irq, NULL,
+ cpr3_irq_handler,
+ IRQF_ONESHOT |
+ IRQF_TRIGGER_RISING,
+ "cpr3", ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "could not request IRQ %d, rc=%d\n",
+ ctrl->irq, rc);
+ goto free_regulators;
+ }
}
- if (ctrl->supports_hw_closed_loop) {
+ if (ctrl->supports_hw_closed_loop &&
+ ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
rc = devm_request_threaded_irq(dev, ctrl->ceiling_irq, NULL,
cpr3_ceiling_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
diff --git a/drivers/regulator/cpr3-regulator.h b/drivers/regulator/cpr3-regulator.h
index 4e098e7d7df1..d7729641fcf6 100644
--- a/drivers/regulator/cpr3-regulator.h
+++ b/drivers/regulator/cpr3-regulator.h
@@ -105,7 +105,10 @@ struct cpr4_sdelta {
* @mem_acc_volt: The mem-acc-supply voltage in corners
* @proc_freq: Processor frequency in Hertz. For CPR rev. 3 and 4
* conrollers, this field is only used by platform specific
- * CPR3 driver for interpolation.
+ * CPR3 driver for interpolation. For CPRh-compliant
+ * controllers, this frequency is also utilized by the
+ * clock driver to determine the corner to CPU clock
+ * frequency mappings.
* @cpr_fuse_corner: Fused corner index associated with this virtual corner
* (only used by platform specific CPR3 driver for
* mapping purposes)
@@ -125,6 +128,9 @@ struct cpr4_sdelta {
* E.g. a value of 900 would imply that the adjustment for
* this corner should be 90% (900/1000) of that for the
* reference corner.
+ * @use_open_loop: Boolean indicating that open-loop (i.e CPR disabled) as
+ * opposed to closed-loop operation must be used for this
+ * corner on CPRh controllers.
* @sdelta: The CPR4 controller specific data for this corner. This
* field is applicable for CPR4 controllers.
*
@@ -149,6 +155,22 @@ struct cpr3_corner {
u32 ro_mask;
u32 irq_en;
int aging_derate;
+ bool use_open_loop;
+ struct cpr4_sdelta *sdelta;
+};
+
+/**
+ * struct cprh_corner_band - CPRh controller specific data structure which
+ * encapsulates the range of corners and the SDELTA
+ * adjustment table to be applied to the corners within
+ * the min and max bounds of the corner band.
+ * @corner: Corner number which defines the corner band boundary
+ * @sdelta: The SDELTA adjustment table which contains core-count
+ * and temp based margin adjustments that are applicable
+ * to the corner band.
+ */
+struct cprh_corner_band {
+ int corner;
struct cpr4_sdelta *sdelta;
};
@@ -174,6 +196,9 @@ struct cpr3_corner {
* manage LDO retention bypass state
* @corner: Array of all corners supported by this CPR3 regulator
* @corner_count: The number of elements in the corner array
+ * @corner_band: Array of all corner bands supported by CPRh compatible
+ * controllers
+ * @corner_band_count: The number of elements in the corner band array
* @platform_fuses: Pointer to platform specific CPR fuse data (only used by
* platform specific CPR3 driver)
* @speed_bin_fuse: Value read from the speed bin fuse parameter
@@ -201,6 +226,17 @@ struct cpr3_corner {
* support is not required.
* @speed_bin_offset: The device tree property array offset for the selected
* speed bin
+ * @fuse_combo_corner_band_sum: The sum of the corner band counts across all
+ * fuse combos
+ * @fuse_combo_corner_band_offset: The device tree property array offset for
+ * the corner band count corresponding to the selected
+ * fuse combo
+ * @speed_bin_corner_band_sum: The sum of the corner band counts across all
+ * speed bins. This may be specified as 0 if per speed bin
+ * parsing support is not required
+ * @speed_bin_corner_band_offset: The device tree property array offset for the
+ * corner band count corresponding to the selected speed
+ * bin
* @pd_bypass_mask: Bit mask of power domains associated with this CPR3
* regulator
* @dynamic_floor_corner: Index identifying the voltage corner for the CPR3
@@ -270,6 +306,8 @@ struct cpr3_regulator {
struct regulator *ldo_ret_regulator;
struct cpr3_corner *corner;
int corner_count;
+ struct cprh_corner_band *corner_band;
+ u32 corner_band_count;
void *platform_fuses;
int speed_bin_fuse;
@@ -283,6 +321,10 @@ struct cpr3_regulator {
int fuse_combo_offset;
int speed_bin_corner_sum;
int speed_bin_offset;
+ int fuse_combo_corner_band_sum;
+ int fuse_combo_corner_band_offset;
+ int speed_bin_corner_band_sum;
+ int speed_bin_corner_band_offset;
u32 pd_bypass_mask;
int dynamic_floor_corner;
bool uses_dynamic_floor;
@@ -394,10 +436,12 @@ enum cpr3_count_mode {
* enum cpr_controller_type - supported CPR controller hardware types
* %CPR_CTRL_TYPE_CPR3: HW has CPR3 controller
* %CPR_CTRL_TYPE_CPR4: HW has CPR4 controller
+ * %CPR_CTRL_TYPE_CPRH: HW has CPRh controller
*/
enum cpr_controller_type {
CPR_CTRL_TYPE_CPR3,
CPR_CTRL_TYPE_CPR4,
+ CPR_CTRL_TYPE_CPRH,
};
/**
@@ -427,6 +471,8 @@ struct cpr3_aging_sensor_info {
* struct cpr3_controller - CPR3 controller data structure
* @dev: Device pointer for the CPR3 controller device
* @name: Unique name for the CPR3 controller
+ * @ctrl_id: Controller ID corresponding to the VDD supply number
+ * that this CPR3 controller manages.
* @cpr_ctrl_base: Virtual address of the CPR3 controller base register
* @fuse_base: Virtual address of fuse row 0
* @list: list head used in a global cpr3-regulator list so that
@@ -475,6 +521,11 @@ struct cpr3_aging_sensor_info {
* or equal to the APM threshold voltage
* @apm_low_supply: APM supply to configure if the VDD voltage is less than
* the APM threshold voltage
+ * @base_volt: Minimum voltage in microvolts supported by the VDD
+ * supply managed by this CPR controller
+ * @corner_switch_delay_time: The delay time in nanoseconds used by the CPR
+ * controller to wait for voltage settling before
+ * acknowledging the OSM block after corner changes
* @cpr_clock_rate: CPR reference clock frequency in Hz.
* @sensor_time: The time in nanoseconds that each sensor takes to
* perform a measurement.
@@ -597,6 +648,7 @@ struct cpr3_aging_sensor_info {
struct cpr3_controller {
struct device *dev;
const char *name;
+ int ctrl_id;
void __iomem *cpr_ctrl_base;
void __iomem *fuse_base;
struct list_head list;
@@ -623,6 +675,8 @@ struct cpr3_controller {
int apm_adj_volt;
enum msm_apm_supply apm_high_supply;
enum msm_apm_supply apm_low_supply;
+ int base_volt;
+ u32 corner_switch_delay_time;
u32 cpr_clock_rate;
u32 sensor_time;
u32 loop_time;
@@ -710,6 +764,8 @@ int cpr3_parse_array_property(struct cpr3_regulator *vreg,
const char *prop_name, int tuple_size, u32 *out);
int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
const char *prop_name, int tuple_size, u32 *out);
+int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
+ const char *prop_name, int tuple_size, u32 *out);
int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg);
int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
u32 *out_value, u32 value_min, u32 value_max);
@@ -729,6 +785,8 @@ int cpr3_voltage_adjustment(int ro_scale, int quot_adjust);
int cpr3_parse_closed_loop_voltage_adjustments(struct cpr3_regulator *vreg,
u64 *ro_sel, int *volt_adjust,
int *volt_adjust_fuse, int *ro_scale);
+int cpr4_parse_core_count_temp_voltage_adj(struct cpr3_regulator *vreg,
+ bool use_corner_band);
int cpr3_apm_init(struct cpr3_controller *ctrl);
int cpr3_mem_acc_init(struct cpr3_regulator *vreg);
@@ -802,6 +860,13 @@ static inline int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
return -EPERM;
}
+static inline int cpr3_parse_corner_band_array_property(
+ struct cpr3_regulator *vreg, const char *prop_name,
+ int tuple_size, u32 *out)
+{
+ return -EPERM;
+}
+
static inline int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
{
return -EPERM;
@@ -880,6 +945,12 @@ static inline int cpr3_parse_closed_loop_voltage_adjustments(
return 0;
}
+static inline int cpr4_parse_core_count_temp_voltage_adj(
+ struct cpr3_regulator *vreg, bool use_corner_band)
+{
+ return 0;
+}
+
static inline int cpr3_apm_init(struct cpr3_controller *ctrl)
{
return 0;
diff --git a/drivers/regulator/cpr3-util.c b/drivers/regulator/cpr3-util.c
index 390436faf91d..46e4045bbd6b 100644
--- a/drivers/regulator/cpr3-util.c
+++ b/drivers/regulator/cpr3-util.c
@@ -434,6 +434,89 @@ int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
}
/**
+ * cpr3_parse_corner_band_array_property() - fill a per-corner band array
+ * from a portion of the values specified for a device tree
+ * property
+ * @vreg: Pointer to the CPR3 regulator
+ * @prop_name: The name of the device tree property to read from
+ * @tuple_size: The number of elements in each per-corner band tuple
+ * @out: Output data array which must be of size:
+ * tuple_size * vreg->corner_band_count
+ *
+ * cpr3_parse_common_corner_data() must be called for vreg before this function
+ * is called so that fuse combo and speed bin size elements are initialized.
+ * In addition, corner band fuse combo and speed bin sum and offset elements
+ * must be initialized prior to executing this function.
+ *
+ * Three formats are supported for the device tree property:
+ * 1. Length == tuple_size * vreg->corner_band_count
+ * (reading begins at index 0)
+ * 2. Length == tuple_size * vreg->fuse_combo_corner_band_sum
+ * (reading begins at index tuple_size *
+ * vreg->fuse_combo_corner_band_offset)
+ * 3. Length == tuple_size * vreg->speed_bin_corner_band_sum
+ * (reading begins at index tuple_size *
+ * vreg->speed_bin_corner_band_offset)
+ *
+ * All other property lengths are treated as errors.
+ *
+ * Return: 0 on success, errno on failure
+ */
+int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
+ const char *prop_name, int tuple_size, u32 *out)
+{
+ struct device_node *node = vreg->of_node;
+ int len = 0;
+ int i, offset, rc;
+
+ if (!of_find_property(node, prop_name, &len)) {
+ cpr3_err(vreg, "property %s is missing\n", prop_name);
+ return -EINVAL;
+ }
+
+ if (len == tuple_size * vreg->corner_band_count * sizeof(u32)) {
+ offset = 0;
+ } else if (len == tuple_size * vreg->fuse_combo_corner_band_sum
+ * sizeof(u32)) {
+ offset = tuple_size * vreg->fuse_combo_corner_band_offset;
+ } else if (vreg->speed_bin_corner_band_sum > 0 &&
+ len == tuple_size * vreg->speed_bin_corner_band_sum *
+ sizeof(u32)) {
+ offset = tuple_size * vreg->speed_bin_corner_band_offset;
+ } else {
+ if (vreg->speed_bin_corner_band_sum > 0)
+ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
+ prop_name, len,
+ tuple_size * vreg->corner_band_count *
+ sizeof(u32),
+ tuple_size * vreg->speed_bin_corner_band_sum
+ * sizeof(u32),
+ tuple_size * vreg->fuse_combo_corner_band_sum
+ * sizeof(u32));
+ else
+ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
+ prop_name, len,
+ tuple_size * vreg->corner_band_count *
+ sizeof(u32),
+ tuple_size * vreg->fuse_combo_corner_band_sum
+ * sizeof(u32));
+ return -EINVAL;
+ }
+
+ for (i = 0; i < tuple_size * vreg->corner_band_count; i++) {
+ rc = of_property_read_u32_index(node, prop_name, offset + i,
+ &out[i]);
+ if (rc) {
+ cpr3_err(vreg, "error reading property %s, rc=%d\n",
+ prop_name, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/**
* cpr3_parse_common_corner_data() - parse common CPR3 properties relating to
* the corners supported by a CPR3 regulator from device tree
* @vreg: Pointer to the CPR3 regulator
@@ -595,8 +678,15 @@ int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
kfree(speed_bin_corners);
}
- vreg->corner = devm_kcalloc(ctrl->dev, vreg->corner_count,
- sizeof(*vreg->corner), GFP_KERNEL);
+ /*
+ * In CPRh compliant controllers an additional corner is
+ * allocated to correspond to the APM crossover voltage
+ */
+ vreg->corner = devm_kcalloc(ctrl->dev, ctrl->ctrl_type ==
+ CPR_CTRL_TYPE_CPRH ?
+ vreg->corner_count + 1 :
+ vreg->corner_count,
+ sizeof(*vreg->corner), GFP_KERNEL);
temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
if (!vreg->corner || !temp)
return -ENOMEM;
@@ -947,15 +1037,6 @@ int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-aging-ref-voltage",
&ctrl->aging_ref_volt);
- ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
- if (IS_ERR(ctrl->vdd_regulator)) {
- rc = PTR_ERR(ctrl->vdd_regulator);
- if (rc != -EPROBE_DEFER)
- cpr3_err(ctrl, "unable request vdd regulator, rc=%d\n",
- rc);
- return rc;
- }
-
if (of_find_property(ctrl->dev->of_node, "clock-names", NULL)) {
ctrl->core_clk = devm_clk_get(ctrl->dev, "core_clk");
if (IS_ERR(ctrl->core_clk)) {
@@ -967,6 +1048,24 @@ int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
}
}
+ /*
+ * Regulator device handles are not necessary for CPRh controllers
+ * since communication with the regulators is completely managed
+ * in hardware.
+ */
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ return rc;
+
+ ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
+ if (IS_ERR(ctrl->vdd_regulator)) {
+ rc = PTR_ERR(ctrl->vdd_regulator);
+ if (rc != -EPROBE_DEFER) {
+ cpr3_err(ctrl, "unable request vdd regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
"system");
if (IS_ERR(ctrl->system_regulator)) {
@@ -1517,3 +1616,234 @@ int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
kfree(temp);
return rc;
}
+
+/**
+ * cpr4_load_core_and_temp_adj() - parse amount of voltage adjustment for
+ * per-online-core and per-temperature voltage adjustment for a
+ * given corner or corner band from device tree.
+ * @vreg: Pointer to the CPR3 regulator
+ * @num: Corner number or corner band number
+ * @use_corner_band: Boolean indicating if the CPR3 regulator supports
+ * adjustments per corner band
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_load_core_and_temp_adj(struct cpr3_regulator *vreg,
+ int num, bool use_corner_band)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr4_sdelta *sdelta;
+ int sdelta_size, i, j, pos, rc = 0;
+ char str[75];
+ size_t buflen;
+ char *buf;
+
+ sdelta = use_corner_band ? vreg->corner_band[num].sdelta :
+ vreg->corner[num].sdelta;
+
+ if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
+ /* corner doesn't need sdelta table */
+ sdelta->max_core_count = 0;
+ sdelta->temp_band_count = 0;
+ return rc;
+ }
+
+ sdelta_size = sdelta->max_core_count * sdelta->temp_band_count;
+ snprintf(str, sizeof(str), use_corner_band ?
+ "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n"
+ : "corner=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
+ num, sdelta->max_core_count,
+ sdelta->temp_band_count, sdelta_size);
+
+ cpr3_debug(vreg, "%s", str);
+
+ sdelta->table = devm_kcalloc(ctrl->dev, sdelta_size,
+ sizeof(*sdelta->table), GFP_KERNEL);
+ if (!sdelta->table)
+ return -ENOMEM;
+
+ snprintf(str, sizeof(str), use_corner_band ?
+ "qcom,cpr-corner-band%d-temp-core-voltage-adjustment" :
+ "qcom,cpr-corner%d-temp-core-voltage-adjustment",
+ num + CPR3_CORNER_OFFSET);
+
+ rc = cpr3_parse_array_property(vreg, str, sdelta_size,
+ sdelta->table);
+ if (rc) {
+ cpr3_err(vreg, "could not load %s, rc=%d\n", str, rc);
+ return rc;
+ }
+
+ /*
+ * Convert sdelta margins from uV to PMIC steps and apply negation to
+ * follow the SDELTA register semantics.
+ */
+ for (i = 0; i < sdelta_size; i++)
+ sdelta->table[i] = -(sdelta->table[i] / ctrl->step_volt);
+
+ buflen = sizeof(*buf) * sdelta_size * (MAX_CHARS_PER_INT + 2);
+ buf = kzalloc(buflen, GFP_KERNEL);
+ if (!buf)
+ return rc;
+
+ for (i = 0; i < sdelta->max_core_count; i++) {
+ for (j = 0, pos = 0; j < sdelta->temp_band_count; j++)
+ pos += scnprintf(buf + pos, buflen - pos, " %u",
+ sdelta->table[i * sdelta->max_core_count + j]);
+ cpr3_debug(vreg, "sdelta[%d]:%s\n", i, buf);
+ }
+
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * cpr4_parse_core_count_temp_voltage_adj() - parse configuration data for
+ * per-online-core and per-temperature voltage adjustment for
+ * a CPR3 regulator from device tree.
+ * @vreg: Pointer to the CPR3 regulator
+ * @use_corner_band: Boolean indicating if the CPR3 regulator supports
+ * adjustments per corner band
+ *
+ * This function supports parsing of per-online-core and per-temperature
+ * adjustments per corner or per corner band. CPR controllers which support
+ * corner bands apply the same adjustments to all corners within a corner band.
+ *
+ * Return: 0 on success, errno on failure
+ */
+int cpr4_parse_core_count_temp_voltage_adj(
+ struct cpr3_regulator *vreg, bool use_corner_band)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct device_node *node = vreg->of_node;
+ struct cpr3_corner *corner;
+ struct cpr4_sdelta *sdelta;
+ int i, sdelta_table_count, rc = 0;
+ int *allow_core_count_adj = NULL, *allow_temp_adj = NULL;
+ char prop_str[75];
+
+ if (of_find_property(node, use_corner_band ?
+ "qcom,corner-band-allow-temp-adjustment"
+ : "qcom,corner-allow-temp-adjustment", NULL)) {
+ if (!ctrl->allow_temp_adj) {
+ cpr3_err(ctrl, "Temperature adjustment configurations missing\n");
+ return -EINVAL;
+ }
+
+ vreg->allow_temp_adj = true;
+ }
+
+ if (of_find_property(node, use_corner_band ?
+ "qcom,corner-band-allow-core-count-adjustment"
+ : "qcom,corner-allow-core-count-adjustment",
+ NULL)) {
+ rc = of_property_read_u32(node, "qcom,max-core-count",
+ &vreg->max_core_count);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,max-core-count, rc=%d\n",
+ rc);
+ return -EINVAL;
+ }
+
+ vreg->allow_core_count_adj = true;
+ ctrl->allow_core_count_adj = true;
+ }
+
+ if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) {
+ /*
+ * Both per-online-core and temperature based adjustments are
+ * disabled for this regulator.
+ */
+ return 0;
+ } else if (!vreg->allow_core_count_adj) {
+ /*
+ * Only per-temperature voltage adjusments are allowed.
+ * Keep max core count value as 1 to allocate SDELTA.
+ */
+ vreg->max_core_count = 1;
+ }
+
+ if (vreg->allow_core_count_adj) {
+ allow_core_count_adj = kcalloc(vreg->corner_count,
+ sizeof(*allow_core_count_adj),
+ GFP_KERNEL);
+ if (!allow_core_count_adj)
+ return -ENOMEM;
+
+ snprintf(prop_str, sizeof(prop_str), use_corner_band ?
+ "qcom,corner-band-allow-core-count-adjustment" :
+ "qcom,corner-allow-core-count-adjustment");
+
+ rc = use_corner_band ?
+ cpr3_parse_corner_band_array_property(vreg, prop_str,
+ 1, allow_core_count_adj) :
+ cpr3_parse_corner_array_property(vreg, prop_str,
+ 1, allow_core_count_adj);
+ if (rc) {
+ cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
+ rc);
+ goto done;
+ }
+ }
+
+ if (vreg->allow_temp_adj) {
+ allow_temp_adj = kcalloc(vreg->corner_count,
+ sizeof(*allow_temp_adj), GFP_KERNEL);
+ if (!allow_temp_adj) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ snprintf(prop_str, sizeof(prop_str), use_corner_band ?
+ "qcom,corner-band-allow-temp-adjustment" :
+ "qcom,corner-allow-temp-adjustment");
+
+ rc = use_corner_band ?
+ cpr3_parse_corner_band_array_property(vreg, prop_str,
+ 1, allow_temp_adj) :
+ cpr3_parse_corner_array_property(vreg, prop_str,
+ 1, allow_temp_adj);
+ if (rc) {
+ cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
+ rc);
+ goto done;
+ }
+ }
+
+ sdelta_table_count = use_corner_band ? vreg->corner_band_count :
+ vreg->corner_count;
+
+ for (i = 0; i < sdelta_table_count; i++) {
+ sdelta = devm_kzalloc(ctrl->dev, sizeof(*corner->sdelta),
+ GFP_KERNEL);
+ if (!sdelta) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ if (allow_core_count_adj)
+ sdelta->allow_core_count_adj = allow_core_count_adj[i];
+ if (allow_temp_adj)
+ sdelta->allow_temp_adj = allow_temp_adj[i];
+ sdelta->max_core_count = vreg->max_core_count;
+ sdelta->temp_band_count = ctrl->temp_band_count;
+
+ if (use_corner_band)
+ vreg->corner_band[i].sdelta = sdelta;
+ else
+ vreg->corner[i].sdelta = sdelta;
+
+ rc = cpr4_load_core_and_temp_adj(vreg, i, use_corner_band);
+ if (rc) {
+ cpr3_err(vreg, "corner/band %d core and temp adjustment loading failed, rc=%d\n",
+ i, rc);
+ goto done;
+ }
+ }
+
+done:
+ kfree(allow_core_count_adj);
+ kfree(allow_temp_adj);
+
+ return rc;
+}
diff --git a/drivers/regulator/cpr4-apss-regulator.c b/drivers/regulator/cpr4-apss-regulator.c
index 5b8104da7c10..c5b66718a7dd 100644
--- a/drivers/regulator/cpr4-apss-regulator.c
+++ b/drivers/regulator/cpr4-apss-regulator.c
@@ -821,188 +821,6 @@ static int cpr4_apss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
}
/**
- * cpr4_load_corner_core_and_temp_adj() - parse amount of voltage adjustment for
- * per-online-core and per-temperature voltage adjustment for a
- * given corner from device tree.
- * @vreg: Pointer to the CPR3 regulator
- *
- * Return: 0 on success, errno on failure
- */
-static int cpr4_load_corner_core_and_temp_adj(struct cpr3_regulator *vreg,
- int corner_num)
-{
- struct cpr3_controller *ctrl = vreg->thread->ctrl;
- struct cpr3_corner *corner = &vreg->corner[corner_num];
- struct cpr4_sdelta *sdelta = corner->sdelta;
- int sdelta_size, i, rc = 0;
- char prop_str[60];
-
- if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
- /* corner doesn't need sdelta table */
- sdelta->max_core_count = 0;
- sdelta->temp_band_count = 0;
- return rc;
- }
-
- sdelta_size = sdelta->max_core_count * sdelta->temp_band_count;
- cpr3_debug(vreg, "corner=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
- corner_num, sdelta->max_core_count,
- sdelta->temp_band_count, sdelta_size);
-
- sdelta->table = devm_kcalloc(ctrl->dev, sdelta_size,
- sizeof(*sdelta->table), GFP_KERNEL);
- if (!sdelta->table)
- return -ENOMEM;
-
- snprintf(prop_str, sizeof(prop_str),
- "qcom,cpr-corner%d-temp-core-voltage-adjustment",
- corner_num + CPR3_CORNER_OFFSET);
-
- rc = cpr3_parse_array_property(vreg, prop_str, sdelta_size,
- sdelta->table);
- if (rc) {
- cpr3_err(vreg, "could not load %s, rc=%d\n", prop_str, rc);
- return rc;
- }
-
- /*
- * Convert sdelta margins from uV to PMIC steps and apply negation to
- * follow the SDELTA register semantics.
- */
- for (i = 0; i < sdelta_size; i++)
- sdelta->table[i] = -(sdelta->table[i] / ctrl->step_volt);
-
- return rc;
-}
-
-/**
- * cpr4_apss_parse_core_count_temp_voltage_adj() - parse configuration data for
- * per-online-core and per-temperature voltage adjustment for CPR3
- * regulator from device tree.
- * @vreg: Pointer to the CPR3 regulator
- *
- * Return: 0 on success, errno on failure
- */
-static int cpr4_apss_parse_core_count_temp_voltage_adj(
- struct cpr3_regulator *vreg)
-{
- struct cpr3_controller *ctrl = vreg->thread->ctrl;
- struct device_node *node = vreg->of_node;
- struct cpr3_corner *corner;
- struct cpr4_sdelta *sdelta;
- int i, rc = 0;
- int *allow_core_count_adj = NULL, *allow_temp_adj = NULL;
-
- if (of_find_property(node, "qcom,corner-allow-temp-adjustment", NULL)) {
- if (!ctrl->allow_temp_adj) {
- cpr3_err(ctrl, "Temperature adjustment configurations missing\n");
- return -EINVAL;
- }
-
- vreg->allow_temp_adj = true;
- }
-
- if (of_find_property(node, "qcom,corner-allow-core-count-adjustment",
- NULL)) {
- rc = of_property_read_u32(node, "qcom,max-core-count",
- &vreg->max_core_count);
- if (rc) {
- cpr3_err(vreg, "error reading qcom,max-num-cpus, rc=%d\n",
- rc);
- return -EINVAL;
- }
-
- if (vreg->max_core_count <= 0 || vreg->max_core_count
- > MSMTITANIUM_APSS_CPR_SDELTA_CORE_COUNT) {
- cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
- vreg->max_core_count);
- return -EINVAL;
- }
-
- vreg->allow_core_count_adj = true;
- ctrl->allow_core_count_adj = true;
- }
-
- if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) {
- /*
- * Both per-online-core and temperature based adjustments are
- * disabled for this regualtor.
- */
- return 0;
- } else if (!vreg->allow_core_count_adj) {
- /*
- * Only per-temperature voltage adjusments are allowed.
- * Keep max core count value as 1 to allocate SDELTA.
- */
- vreg->max_core_count = 1;
- }
-
- if (vreg->allow_core_count_adj) {
- allow_core_count_adj = kcalloc(vreg->corner_count,
- sizeof(*allow_core_count_adj),
- GFP_KERNEL);
- if (!allow_core_count_adj)
- return -ENOMEM;
-
- rc = cpr3_parse_corner_array_property(vreg,
- "qcom,corner-allow-core-count-adjustment",
- 1, allow_core_count_adj);
- if (rc) {
- cpr3_err(vreg, "qcom,corner-allow-core-count-adjustment reading failed, rc=%d\n",
- rc);
- goto done;
- }
- }
-
- if (vreg->allow_temp_adj) {
- allow_temp_adj = kcalloc(vreg->corner_count,
- sizeof(*allow_temp_adj), GFP_KERNEL);
- if (!allow_temp_adj) {
- rc = -ENOMEM;
- goto done;
- }
-
- rc = cpr3_parse_corner_array_property(vreg,
- "qcom,corner-allow-temp-adjustment",
- 1, allow_temp_adj);
- if (rc) {
- cpr3_err(vreg, "qcom,corner-allow-temp-adjustment reading failed, rc=%d\n",
- rc);
- goto done;
- }
- }
-
- for (i = 0; i < vreg->corner_count; i++) {
- corner = &vreg->corner[i];
- sdelta = devm_kzalloc(ctrl->dev, sizeof(*corner->sdelta),
- GFP_KERNEL);
- if (!sdelta) {
- rc = -ENOMEM;
- goto done;
- }
-
- if (allow_core_count_adj)
- sdelta->allow_core_count_adj = allow_core_count_adj[i];
- if (allow_temp_adj)
- sdelta->allow_temp_adj = allow_temp_adj[i];
- sdelta->max_core_count = vreg->max_core_count;
- sdelta->temp_band_count = ctrl->temp_band_count;
- corner->sdelta = sdelta;
- rc = cpr4_load_corner_core_and_temp_adj(vreg, i);
- if (rc) {
- cpr3_err(vreg, "corner %d core and temp adjustment loading failed: rc=%d\n",
- i, rc);
- goto done;
- }
- }
-
-done:
- kfree(allow_core_count_adj);
- kfree(allow_temp_adj);
- return rc;
-}
-
-/**
* cpr4_apss_parse_boost_properties() - parse configuration data for boost
* voltage adjustment for CPR3 regulator from device tree.
* @vreg: Pointer to the CPR3 regulator
@@ -1217,13 +1035,21 @@ static int cpr4_apss_init_regulator(struct cpr3_regulator *vreg)
return rc;
}
- rc = cpr4_apss_parse_core_count_temp_voltage_adj(vreg);
+ rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
if (rc) {
cpr3_err(vreg, "unable to parse temperature and core count voltage adjustments, rc=%d\n",
rc);
return rc;
}
+ if (vreg->allow_core_count_adj && (vreg->max_core_count <= 0
+ || vreg->max_core_count >
+ MSMTITANIUM_APSS_CPR_SDELTA_CORE_COUNT)) {
+ cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
+ vreg->max_core_count);
+ return -EINVAL;
+ }
+
rc = cpr4_apss_parse_boost_properties(vreg);
if (rc) {
cpr3_err(vreg, "unable to parse boost adjustments, rc=%d\n",
diff --git a/drivers/regulator/cprh-kbss-regulator.c b/drivers/regulator/cprh-kbss-regulator.c
new file mode 100644
index 000000000000..11995a8c6e62
--- /dev/null
+++ b/drivers/regulator/cprh-kbss-regulator.c
@@ -0,0 +1,1485 @@
+/*
+ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define MSMCOBALT_KBSS_FUSE_CORNERS 4
+
+/**
+ * struct cprh_msmcobalt_kbss_fuses - KBSS specific fuse data for MSMCOBALT
+ * @ro_sel: Ring oscillator select fuse parameter value for each
+ * fuse corner
+ * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
+ * for each fuse corner (raw, not converted to a voltage)
+ * @target_quot: CPR target quotient fuse parameter value for each fuse
+ * corner
+ * @quot_offset: CPR target quotient offset fuse parameter value for each
+ * fuse corner (raw, not unpacked) used for target quotient
+ * interpolation
+ * @speed_bin: Application processor speed bin fuse parameter value for
+ * the given chip
+ * @cpr_fusing_rev: CPR fusing revision fuse parameter value
+ *
+ * This struct holds the values for all of the fuses read from memory.
+ */
+struct cprh_msmcobalt_kbss_fuses {
+ u64 ro_sel[MSMCOBALT_KBSS_FUSE_CORNERS];
+ u64 init_voltage[MSMCOBALT_KBSS_FUSE_CORNERS];
+ u64 target_quot[MSMCOBALT_KBSS_FUSE_CORNERS];
+ u64 quot_offset[MSMCOBALT_KBSS_FUSE_CORNERS];
+ u64 speed_bin;
+ u64 cpr_fusing_rev;
+};
+
+/*
+ * Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 with speed bin fuse = 0.
+ */
+#define CPRH_MSMCOBALT_KBSS_FUSE_COMBO_COUNT 8
+
+/*
+ * Constants which define the name of each fuse corner.
+ */
+enum cprh_msmcobalt_kbss_fuse_corner {
+ CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS = 0,
+ CPRH_MSMCOBALT_KBSS_FUSE_CORNER_SVS = 1,
+ CPRH_MSMCOBALT_KBSS_FUSE_CORNER_NOM = 2,
+ CPRH_MSMCOBALT_KBSS_FUSE_CORNER_TURBO_L2 = 3,
+};
+
+static const char * const cprh_msmcobalt_kbss_fuse_corner_name[] = {
+ [CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS] = "LowSVS",
+ [CPRH_MSMCOBALT_KBSS_FUSE_CORNER_SVS] = "SVS",
+ [CPRH_MSMCOBALT_KBSS_FUSE_CORNER_NOM] = "NOM",
+ [CPRH_MSMCOBALT_KBSS_FUSE_CORNER_TURBO_L2] = "TURBO_L2",
+};
+
+/* KBSS cluster IDs */
+#define MSMCOBALT_KBSS_POWER_CLUSTER_ID 0
+#define MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID 1
+
+/* KBSS controller IDs */
+#define MSMCOBALT_KBSS_MIN_CONTROLLER_ID 0
+#define MSMCOBALT_KBSS_MAX_CONTROLLER_ID 1
+
+/*
+ * MSMCOBALT KBSS fuse parameter locations:
+ *
+ * Structs are organized with the following dimensions:
+ * Outer: 0 or 1 for power or performance cluster
+ * Middle: 0 to 3 for fuse corners from lowest to highest corner
+ * Inner: large enough to hold the longest set of parameter segments which
+ * fully defines a fuse parameter, +1 (for NULL termination).
+ * Each segment corresponds to a contiguous group of bits from a
+ * single fuse row. These segments are concatentated together in
+ * order to form the full fuse parameter value. The segments for
+ * a given parameter may correspond to different fuse rows.
+ *
+ */
+static const struct cpr3_fuse_param
+msmcobalt_kbss_ro_sel_param[2][MSMCOBALT_KBSS_FUSE_CORNERS][2] = {
+ [MSMCOBALT_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 12, 15}, {} },
+ {{67, 8, 11}, {} },
+ {{67, 4, 7}, {} },
+ {{67, 0, 3}, {} },
+ },
+ [MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 26, 29}, {} },
+ {{69, 22, 25}, {} },
+ {{69, 18, 21}, {} },
+ {{69, 14, 17}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msmcobalt_kbss_init_voltage_param[2][MSMCOBALT_KBSS_FUSE_CORNERS][2] = {
+ [MSMCOBALT_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 34, 39}, {} },
+ {{67, 28, 33}, {} },
+ {{67, 22, 27}, {} },
+ {{67, 16, 21}, {} },
+ },
+ [MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 48, 53}, {} },
+ {{69, 42, 47}, {} },
+ {{69, 36, 41}, {} },
+ {{69, 30, 35}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msmcobalt_kbss_target_quot_param[2][MSMCOBALT_KBSS_FUSE_CORNERS][3] = {
+ [MSMCOBALT_KBSS_POWER_CLUSTER_ID] = {
+ {{68, 18, 29}, {} },
+ {{68, 6, 17}, {} },
+ {{67, 58, 63}, {68, 0, 5} },
+ {{67, 46, 57}, {} },
+ },
+ [MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{70, 32, 43}, {} },
+ {{70, 20, 31}, {} },
+ {{70, 8, 19}, {} },
+ {{69, 60, 63}, {70, 0, 7}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msmcobalt_kbss_quot_offset_param[2][MSMCOBALT_KBSS_FUSE_CORNERS][3] = {
+ [MSMCOBALT_KBSS_POWER_CLUSTER_ID] = {
+ {{} },
+ {{68, 63, 63}, {69, 0, 5}, {} },
+ {{68, 56, 62}, {} },
+ {{68, 49, 55}, {} },
+ },
+ [MSMCOBALT_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{} },
+ {{71, 13, 15}, {71, 21, 24}, {} },
+ {{71, 6, 12}, {} },
+ {{70, 63, 63}, {71, 0, 5}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param msmcobalt_cpr_fusing_rev_param[] = {
+ {39, 51, 53},
+ {},
+};
+
+static const struct cpr3_fuse_param msmcobalt_kbss_speed_bin_param[] = {
+ {38, 29, 31},
+ {},
+};
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for MSMCOBALT v1
+ */
+static const int msmcobalt_kbss_fuse_ref_volt[MSMCOBALT_KBSS_FUSE_CORNERS] = {
+ 632000,
+ 700000,
+ 828000,
+ 1024000,
+};
+
+#define MSMCOBALT_KBSS_FUSE_STEP_VOLT 10000
+#define MSMCOBALT_KBSS_VOLTAGE_FUSE_SIZE 6
+#define MSMCOBALT_KBSS_QUOT_OFFSET_SCALE 5
+
+#define MSMCOBALT_KBSS_POWER_CPR_SENSOR_COUNT 6
+#define MSMCOBALT_KBSS_PERFORMANCE_CPR_SENSOR_COUNT 9
+
+#define MSMCOBALT_KBSS_CPR_CLOCK_RATE 19200000
+
+#define MSMCOBALT_KBSS_MAX_CORNER_BAND_COUNT 4
+#define MSMCOBALT_KBSS_MAX_CORNER_COUNT 40
+
+#define MSMCOBALT_KBSS_CPR_SDELTA_CORE_COUNT 4
+
+#define MSMCOBALT_KBSS_MAX_TEMP_POINTS 3
+#define MSMCOBALT_KBSS_POWER_TEMP_SENSOR_ID_START 1
+#define MSMCOBALT_KBSS_POWER_TEMP_SENSOR_ID_END 5
+#define MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START 6
+#define MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END 11
+
+/**
+ * cprh_msmcobalt_kbss_read_fuse_data() - load KBSS specific fuse parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cprh_msmcobalt_kbss_fuses struct, fills it with
+ * values read out of hardware fuses, and finally copies common fuse values
+ * into the CPR3 regulator struct.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msmcobalt_kbss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cprh_msmcobalt_kbss_fuses *fuse;
+ int i, id, rc;
+
+ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
+ if (!fuse)
+ return -ENOMEM;
+
+ rc = cpr3_read_fuse_param(base, msmcobalt_kbss_speed_bin_param,
+ &fuse->speed_bin);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read speed bin fuse, rc=%d\n", rc);
+ return rc;
+ }
+ cpr3_info(vreg, "speed bin = %llu\n", fuse->speed_bin);
+
+ rc = cpr3_read_fuse_param(base, msmcobalt_cpr_fusing_rev_param,
+ &fuse->cpr_fusing_rev);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read CPR fusing revision fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+ cpr3_info(vreg, "CPR fusing revision = %llu\n", fuse->cpr_fusing_rev);
+
+ id = vreg->thread->ctrl->ctrl_id;
+
+ for (i = 0; i < MSMCOBALT_KBSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ msmcobalt_kbss_init_voltage_param[id][i],
+ &fuse->init_voltage[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base,
+ msmcobalt_kbss_target_quot_param[id][i],
+ &fuse->target_quot[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d target quotient fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base,
+ msmcobalt_kbss_ro_sel_param[id][i],
+ &fuse->ro_sel[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d RO select fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base,
+ msmcobalt_kbss_quot_offset_param[id][i],
+ &fuse->quot_offset[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d quotient offset fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ }
+
+ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
+ if (vreg->fuse_combo >= CPRH_MSMCOBALT_KBSS_FUSE_COMBO_COUNT) {
+ cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
+ vreg->fuse_combo);
+ return -EINVAL;
+ }
+
+ vreg->speed_bin_fuse = fuse->speed_bin;
+ vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
+ vreg->fuse_corner_count = MSMCOBALT_KBSS_FUSE_CORNERS;
+ vreg->platform_fuses = fuse;
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_parse_corner_data() - parse KBSS corner data from device tree
+ * properties of the CPR3 regulator's device node
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_parse_corner_data(struct cpr3_regulator *vreg)
+{
+ int rc;
+
+ rc = cpr3_parse_common_corner_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * A total of MSMCOBALT_KBSS_MAX_CORNER_COUNT - 1 corners
+ * may be specified in device tree as an additional corner
+ * must be allocated to correspond to the APM crossover voltage.
+ */
+ if (vreg->corner_count > MSMCOBALT_KBSS_MAX_CORNER_COUNT - 1) {
+ cpr3_err(vreg, "corner count %d exceeds supported maximum %d\n",
+ vreg->corner_count, MSMCOBALT_KBSS_MAX_CORNER_COUNT - 1);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+/**
+ * cprh_msmcobalt_kbss_calculate_open_loop_voltages() - calculate the open-loop
+ * voltage for each corner of a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * If open-loop voltage interpolation is allowed in device tree, then this
+ * function calculates the open-loop voltage for a given corner using linear
+ * interpolation. This interpolation is performed using the processor
+ * frequencies of the lower and higher Fmax corners along with their fused
+ * open-loop voltages.
+ *
+ * If open-loop voltage interpolation is not allowed, then this function uses
+ * the Fmax fused open-loop voltage for all of the corners associated with a
+ * given fuse corner.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msmcobalt_kbss_calculate_open_loop_voltages(
+ struct cpr3_regulator *vreg)
+{
+ struct device_node *node = vreg->of_node;
+ struct cprh_msmcobalt_kbss_fuses *fuse = vreg->platform_fuses;
+ int i, j, rc = 0;
+ bool allow_interpolation;
+ u64 freq_low, volt_low, freq_high, volt_high;
+ int *fuse_volt;
+ int *fmax_corner;
+
+ fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
+ GFP_KERNEL);
+ fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
+ GFP_KERNEL);
+ if (!fuse_volt || !fmax_corner) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
+ msmcobalt_kbss_fuse_ref_volt[i],
+ MSMCOBALT_KBSS_FUSE_STEP_VOLT, fuse->init_voltage[i],
+ MSMCOBALT_KBSS_VOLTAGE_FUSE_SIZE);
+
+ /* Log fused open-loop voltage values for debugging purposes. */
+ cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
+ cprh_msmcobalt_kbss_fuse_corner_name[i],
+ fuse_volt[i]);
+ }
+
+ rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
+ if (rc) {
+ cpr3_err(vreg, "fused open-loop voltage adjustment failed, rc=%d\n",
+ rc);
+ goto done;
+ }
+
+ allow_interpolation = of_property_read_bool(node,
+ "qcom,allow-voltage-interpolation");
+
+ for (i = 1; i < vreg->fuse_corner_count; i++) {
+ if (fuse_volt[i] < fuse_volt[i - 1]) {
+ cpr3_info(vreg, "fuse corner %d voltage=%d uV < fuse corner %d voltage=%d uV; overriding: fuse corner %d voltage=%d\n",
+ i, fuse_volt[i], i - 1, fuse_volt[i - 1],
+ i, fuse_volt[i - 1]);
+ fuse_volt[i] = fuse_volt[i - 1];
+ }
+ }
+
+ if (!allow_interpolation) {
+ /* Use fused open-loop voltage for lower frequencies. */
+ for (i = 0; i < vreg->corner_count; i++)
+ vreg->corner[i].open_loop_volt
+ = fuse_volt[vreg->corner[i].cpr_fuse_corner];
+ goto done;
+ }
+
+ /* Determine highest corner mapped to each fuse corner */
+ j = vreg->fuse_corner_count - 1;
+ for (i = vreg->corner_count - 1; i >= 0; i--) {
+ if (vreg->corner[i].cpr_fuse_corner == j) {
+ fmax_corner[j] = i;
+ j--;
+ }
+ }
+ if (j >= 0) {
+ cpr3_err(vreg, "invalid fuse corner mapping\n");
+ rc = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * Interpolation is not possible for corners mapped to the lowest fuse
+ * corner so use the fuse corner value directly.
+ */
+ for (i = 0; i <= fmax_corner[0]; i++)
+ vreg->corner[i].open_loop_volt = fuse_volt[0];
+
+ /* Interpolate voltages for the higher fuse corners. */
+ for (i = 1; i < vreg->fuse_corner_count - 1; i++) {
+ freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
+ volt_low = fuse_volt[i - 1];
+ freq_high = vreg->corner[fmax_corner[i]].proc_freq;
+ volt_high = fuse_volt[i];
+
+ for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
+ vreg->corner[j].open_loop_volt = cpr3_interpolate(
+ freq_low, volt_low, freq_high, volt_high,
+ vreg->corner[j].proc_freq);
+ }
+
+done:
+ if (rc == 0) {
+ cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
+ for (i = 0; i < vreg->corner_count; i++)
+ cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
+ vreg->corner[i].open_loop_volt);
+
+ rc = cpr3_adjust_open_loop_voltages(vreg);
+ if (rc)
+ cpr3_err(vreg, "open-loop voltage adjustment failed, rc=%d\n",
+ rc);
+ }
+
+ kfree(fuse_volt);
+ kfree(fmax_corner);
+ return rc;
+}
+
+/**
+ * cprh_kbss_parse_core_count_temp_adj_properties() - load device tree
+ * properties associated with per-corner-band and temperature
+ * voltage adjustments.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_parse_core_count_temp_adj_properties(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct device_node *node = vreg->of_node;
+ u32 *temp, *combo_corner_bands, *speed_bin_corner_bands;
+ int rc, i, len, temp_point_count;
+
+ vreg->allow_core_count_adj = of_find_property(node,
+ "qcom,corner-band-allow-core-count-adjustment",
+ NULL);
+ vreg->allow_temp_adj = of_find_property(node,
+ "qcom,corner-band-allow-temp-adjustment",
+ NULL);
+
+ if (!vreg->allow_core_count_adj && !vreg->allow_temp_adj)
+ return 0;
+
+ combo_corner_bands = kcalloc(vreg->fuse_combos_supported,
+ sizeof(*combo_corner_bands),
+ GFP_KERNEL);
+ if (!combo_corner_bands)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node, "qcom,cpr-corner-bands",
+ combo_corner_bands,
+ vreg->fuse_combos_supported);
+ if (rc == -EOVERFLOW) {
+ /* Single value case */
+ rc = of_property_read_u32(node, "qcom,cpr-corner-bands",
+ combo_corner_bands);
+ for (i = 1; i < vreg->fuse_combos_supported; i++)
+ combo_corner_bands[i] = combo_corner_bands[0];
+ }
+ if (rc) {
+ cpr3_err(vreg, "error reading property qcom,cpr-corner-bands, rc=%d\n",
+ rc);
+ kfree(combo_corner_bands);
+ return rc;
+ }
+
+ vreg->fuse_combo_corner_band_offset = 0;
+ vreg->fuse_combo_corner_band_sum = 0;
+ for (i = 0; i < vreg->fuse_combos_supported; i++) {
+ vreg->fuse_combo_corner_band_sum += combo_corner_bands[i];
+ if (i < vreg->fuse_combo)
+ vreg->fuse_combo_corner_band_offset +=
+ combo_corner_bands[i];
+ }
+
+ vreg->corner_band_count = combo_corner_bands[vreg->fuse_combo];
+
+ kfree(combo_corner_bands);
+
+ if (vreg->corner_band_count <= 0 ||
+ vreg->corner_band_count > MSMCOBALT_KBSS_MAX_CORNER_BAND_COUNT ||
+ vreg->corner_band_count > vreg->corner_count) {
+ cpr3_err(vreg, "invalid corner band count %d > %d (max) for %d corners\n",
+ vreg->corner_band_count,
+ MSMCOBALT_KBSS_MAX_CORNER_BAND_COUNT,
+ vreg->corner_count);
+ return -EINVAL;
+ }
+
+ vreg->speed_bin_corner_band_offset = 0;
+ vreg->speed_bin_corner_band_sum = 0;
+ if (vreg->speed_bins_supported > 0) {
+ speed_bin_corner_bands = kcalloc(vreg->speed_bins_supported,
+ sizeof(*speed_bin_corner_bands),
+ GFP_KERNEL);
+ if (!speed_bin_corner_bands)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node,
+ "qcom,cpr-speed-bin-corner-bands",
+ speed_bin_corner_bands,
+ vreg->speed_bins_supported);
+ if (rc) {
+ cpr3_err(vreg, "error reading property qcom,cpr-speed-bin-corner-bands, rc=%d\n",
+ rc);
+ kfree(speed_bin_corner_bands);
+ return rc;
+ }
+
+ for (i = 0; i < vreg->speed_bins_supported; i++) {
+ vreg->speed_bin_corner_band_sum +=
+ speed_bin_corner_bands[i];
+ if (i < vreg->speed_bin_fuse)
+ vreg->speed_bin_corner_band_offset +=
+ speed_bin_corner_bands[i];
+ }
+
+ if (speed_bin_corner_bands[vreg->speed_bin_fuse]
+ != vreg->corner_band_count) {
+ cpr3_err(vreg, "qcom,cpr-corner-bands and qcom,cpr-speed-bin-corner-bands conflict on number of corners bands: %d vs %u\n",
+ vreg->corner_band_count,
+ speed_bin_corner_bands[vreg->speed_bin_fuse]);
+ kfree(speed_bin_corner_bands);
+ return -EINVAL;
+ }
+
+ kfree(speed_bin_corner_bands);
+ }
+
+ vreg->corner_band = devm_kcalloc(ctrl->dev,
+ vreg->corner_band_count,
+ sizeof(*vreg->corner_band),
+ GFP_KERNEL);
+
+ temp = kcalloc(vreg->corner_band_count, sizeof(*temp), GFP_KERNEL);
+
+ if (!vreg->corner_band || !temp) {
+ rc = -ENOMEM;
+ goto free_temp;
+ }
+
+ rc = cpr3_parse_corner_band_array_property(vreg,
+ "qcom,cpr-corner-band-map",
+ 1, temp);
+ if (rc) {
+ cpr3_err(vreg, "could not load corner band map, rc=%d\n",
+ rc);
+ goto free_temp;
+ }
+
+ for (i = 1; i < vreg->corner_band_count; i++) {
+ if (temp[i - 1] > temp[i]) {
+ cpr3_err(vreg, "invalid corner band mapping: band %d corner %d, band %d corner %d\n",
+ i - 1, temp[i - 1],
+ i, temp[i]);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+ }
+
+ for (i = 0; i < vreg->corner_band_count; i++)
+ vreg->corner_band[i].corner = temp[i] - CPR3_CORNER_OFFSET;
+
+ if (!of_find_property(ctrl->dev->of_node,
+ "qcom,cpr-temp-point-map", &len)) {
+ /*
+ * Temperature based adjustments are not defined. Single
+ * temperature band is still valid for per-online-core
+ * adjustments.
+ */
+ ctrl->temp_band_count = 1;
+ rc = 0;
+ goto free_temp;
+ }
+
+ if (!vreg->allow_temp_adj) {
+ rc = 0;
+ goto free_temp;
+ }
+
+ temp_point_count = len / sizeof(u32);
+ if (temp_point_count <= 0 || temp_point_count >
+ MSMCOBALT_KBSS_MAX_TEMP_POINTS) {
+ cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
+ temp_point_count, MSMCOBALT_KBSS_MAX_TEMP_POINTS);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+
+ ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
+ sizeof(*ctrl->temp_points), GFP_KERNEL);
+ if (!ctrl->temp_points) {
+ rc = -ENOMEM;
+ goto free_temp;
+ }
+ rc = of_property_read_u32_array(ctrl->dev->of_node,
+ "qcom,cpr-temp-point-map",
+ ctrl->temp_points, temp_point_count);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,cpr-temp-point-map, rc=%d\n",
+ rc);
+ goto free_temp;
+ }
+
+ for (i = 0; i < temp_point_count; i++)
+ cpr3_debug(ctrl, "Temperature Point %d=%d\n", i,
+ ctrl->temp_points[i]);
+
+ /*
+ * If t1, t2, and t3 are the temperature points, then the temperature
+ * bands are: (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
+ */
+ ctrl->temp_band_count = temp_point_count + 1;
+ cpr3_debug(ctrl, "Number of temp bands=%d\n",
+ ctrl->temp_band_count);
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-initial-temp-band",
+ &ctrl->initial_temp_band);
+ if (rc) {
+ cpr3_err(ctrl, "error reading qcom,cpr-initial-temp-band, rc=%d\n",
+ rc);
+ goto free_temp;
+ }
+
+ if (ctrl->initial_temp_band >= ctrl->temp_band_count) {
+ cpr3_err(ctrl, "Initial temperature band value %d should be in range [0 - %d]\n",
+ ctrl->initial_temp_band, ctrl->temp_band_count - 1);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+
+ ctrl->temp_sensor_id_start = ctrl->ctrl_id ==
+ MSMCOBALT_KBSS_POWER_CLUSTER_ID
+ ? MSMCOBALT_KBSS_POWER_TEMP_SENSOR_ID_START :
+ MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START;
+ ctrl->temp_sensor_id_end = ctrl->ctrl_id ==
+ MSMCOBALT_KBSS_POWER_CLUSTER_ID
+ ? MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START :
+ MSMCOBALT_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END;
+ ctrl->allow_temp_adj = true;
+
+ return 0;
+
+free_temp:
+ kfree(temp);
+
+ return rc;
+}
+
+/**
+ * cprh_kbss_apm_threshold_as_corner() - introduce a corner whose floor, open-loop,
+ * and ceiling voltages correspond to the APM threshold voltage.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * The APM corner is utilized as a crossover corner by OSM and CPRh
+ * hardware to determine the correct APM supply selection for the
+ * rest of the corners. This function must be called after all other
+ * functions which load per-corner values.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_apm_threshold_as_corner(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+ struct cprh_corner_band *corner_band;
+ int i, threshold, apm_corner = 0;
+
+ if (!ctrl->apm_threshold_volt) {
+ /* APM voltage threshold corner not required. */
+ return 0;
+ }
+
+ threshold = ctrl->apm_threshold_volt;
+ vreg->corner_count++;
+
+ for (i = vreg->corner_count - 1; i >= 1; i--) {
+ corner = &vreg->corner[i];
+
+ if (threshold >= vreg->corner[i - 1].open_loop_volt) {
+ apm_corner = i;
+ break;
+ }
+
+ memcpy(corner, &vreg->corner[i - 1], sizeof(*corner));
+ }
+
+ corner = &vreg->corner[apm_corner];
+ corner->proc_freq = 0;
+ corner->floor_volt = threshold;
+ corner->ceiling_volt = threshold;
+ corner->open_loop_volt = threshold;
+ corner->use_open_loop = true;
+ cpr3_info(vreg, "APM threshold corner=%d, open-loop=%d\n",
+ apm_corner, threshold);
+
+ /*
+ * Update corner band mappings to account for the inserted
+ * APM crossover corner.
+ */
+ for (i = 0; i < vreg->corner_band_count; i++) {
+ corner_band = &vreg->corner_band[i];
+ if (corner_band->corner >= apm_corner)
+ corner_band->corner++;
+ }
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_adjust_voltages_for_apm() - adjust per-corner floor and ceiling
+ * voltages so that they do not overlap the APM threshold voltage.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * The KBSS memory array power mux (APM) must be configured for a specific
+ * supply based upon where the VDD voltage lies with respect to the APM
+ * threshold voltage. When using CPR hardware closed-loop, the voltage may vary
+ * anywhere between the floor and ceiling voltage without software notification.
+ * Therefore, it is required that the floor to ceiling range for every corner
+ * not intersect the APM threshold voltage. This function adjusts the floor to
+ * ceiling range for each corner which violates this requirement.
+ *
+ * The following algorithm is applied in the case that
+ * floor < threshold <= ceiling:
+ * if open_loop >= threshold - adj, then floor = threshold
+ * else ceiling = threshold - step
+ * where adj = an adjustment factor to ensure sufficient voltage margin and
+ * step = VDD output step size
+ *
+ * The open-loop voltage is also bounded by the new floor or ceiling value as
+ * needed.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+ int i, adj, threshold, prev_ceiling, prev_floor, prev_open_loop;
+
+ if (!ctrl->apm_threshold_volt) {
+ /* APM not being used. */
+ return 0;
+ }
+
+ ctrl->apm_threshold_volt = CPR3_ROUND(ctrl->apm_threshold_volt,
+ ctrl->step_volt);
+ ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
+
+ threshold = ctrl->apm_threshold_volt;
+ adj = ctrl->apm_adj_volt;
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+
+ if (threshold <= corner->floor_volt
+ || threshold > corner->ceiling_volt)
+ continue;
+
+ prev_floor = corner->floor_volt;
+ prev_ceiling = corner->ceiling_volt;
+ prev_open_loop = corner->open_loop_volt;
+
+ if (corner->open_loop_volt >= threshold - adj) {
+ corner->floor_volt = threshold;
+ if (corner->open_loop_volt < corner->floor_volt)
+ corner->open_loop_volt = corner->floor_volt;
+ } else {
+ corner->ceiling_volt = threshold - ctrl->step_volt;
+ }
+
+ cpr3_debug(vreg, "APM threshold=%d, APM adj=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
+ threshold, adj, i, prev_floor, prev_ceiling,
+ prev_open_loop, corner->floor_volt,
+ corner->ceiling_volt, corner->open_loop_volt);
+ }
+
+ return 0;
+}
+
+/**
+ * cprh_msmcobalt_kbss_set_no_interpolation_quotients() - use the fused target
+ * quotient values for lower frequencies.
+ * @vreg: Pointer to the CPR3 regulator
+ * @volt_adjust: Pointer to array of per-corner closed-loop adjustment
+ * voltages
+ * @volt_adjust_fuse: Pointer to array of per-fuse-corner closed-loop
+ * adjustment voltages
+ * @ro_scale: Pointer to array of per-fuse-corner RO scaling factor
+ * values with units of QUOT/V
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msmcobalt_kbss_set_no_interpolation_quotients(
+ struct cpr3_regulator *vreg, int *volt_adjust,
+ int *volt_adjust_fuse, int *ro_scale)
+{
+ struct cprh_msmcobalt_kbss_fuses *fuse = vreg->platform_fuses;
+ u32 quot, ro;
+ int quot_adjust;
+ int i, fuse_corner;
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ fuse_corner = vreg->corner[i].cpr_fuse_corner;
+ quot = fuse->target_quot[fuse_corner];
+ quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
+ volt_adjust_fuse[fuse_corner] +
+ volt_adjust[i]);
+ ro = fuse->ro_sel[fuse_corner];
+ vreg->corner[i].target_quot[ro] = quot + quot_adjust;
+ cpr3_debug(vreg, "corner=%d RO=%u target quot=%u\n",
+ i, ro, quot);
+
+ if (quot_adjust)
+ cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %u --> %u (%d uV)\n",
+ i, ro, quot, vreg->corner[i].target_quot[ro],
+ volt_adjust_fuse[fuse_corner] +
+ volt_adjust[i]);
+ }
+
+ return 0;
+}
+
+/**
+ * cprh_msmcobalt_kbss_calculate_target_quotients() - calculate the CPR target
+ * quotient for each corner of a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * If target quotient interpolation is allowed in device tree, then this
+ * function calculates the target quotient for a given corner using linear
+ * interpolation. This interpolation is performed using the processor
+ * frequencies of the lower and higher Fmax corners along with the fused
+ * target quotient and quotient offset of the higher Fmax corner.
+ *
+ * If target quotient interpolation is not allowed, then this function uses
+ * the Fmax fused target quotient for all of the corners associated with a
+ * given fuse corner.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msmcobalt_kbss_calculate_target_quotients(
+ struct cpr3_regulator *vreg)
+{
+ struct cprh_msmcobalt_kbss_fuses *fuse = vreg->platform_fuses;
+ int rc;
+ bool allow_interpolation;
+ u64 freq_low, freq_high, prev_quot;
+ u64 *quot_low;
+ u64 *quot_high;
+ u32 quot, ro;
+ int i, j, fuse_corner, quot_adjust;
+ int *fmax_corner;
+ int *volt_adjust, *volt_adjust_fuse, *ro_scale;
+
+ /* Log fused quotient values for debugging purposes. */
+ cpr3_info(vreg, "fused LowSVS: quot[%2llu]=%4llu\n",
+ fuse->ro_sel[CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS],
+ fuse->target_quot[CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS]);
+ for (i = CPRH_MSMCOBALT_KBSS_FUSE_CORNER_SVS;
+ i <= CPRH_MSMCOBALT_KBSS_FUSE_CORNER_TURBO_L2; i++)
+ cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
+ cprh_msmcobalt_kbss_fuse_corner_name[i],
+ fuse->ro_sel[i], fuse->target_quot[i],
+ fuse->ro_sel[i], fuse->quot_offset[i] *
+ MSMCOBALT_KBSS_QUOT_OFFSET_SCALE);
+
+ allow_interpolation = of_property_read_bool(vreg->of_node,
+ "qcom,allow-quotient-interpolation");
+
+ volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
+ GFP_KERNEL);
+ volt_adjust_fuse = kcalloc(vreg->fuse_corner_count,
+ sizeof(*volt_adjust_fuse), GFP_KERNEL);
+ ro_scale = kcalloc(vreg->fuse_corner_count, sizeof(*ro_scale),
+ GFP_KERNEL);
+ fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
+ GFP_KERNEL);
+ quot_low = kcalloc(vreg->fuse_corner_count, sizeof(*quot_low),
+ GFP_KERNEL);
+ quot_high = kcalloc(vreg->fuse_corner_count, sizeof(*quot_high),
+ GFP_KERNEL);
+ if (!volt_adjust || !volt_adjust_fuse || !ro_scale ||
+ !fmax_corner || !quot_low || !quot_high) {
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ rc = cpr3_parse_closed_loop_voltage_adjustments(vreg, &fuse->ro_sel[0],
+ volt_adjust, volt_adjust_fuse, ro_scale);
+ if (rc) {
+ cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
+ rc);
+ goto done;
+ }
+
+ if (!allow_interpolation) {
+ /* Use fused target quotients for lower frequencies. */
+ return cprh_msmcobalt_kbss_set_no_interpolation_quotients(
+ vreg, volt_adjust, volt_adjust_fuse, ro_scale);
+ }
+
+ /* Determine highest corner mapped to each fuse corner */
+ j = vreg->fuse_corner_count - 1;
+ for (i = vreg->corner_count - 1; i >= 0; i--) {
+ if (vreg->corner[i].cpr_fuse_corner == j) {
+ fmax_corner[j] = i;
+ j--;
+ }
+ }
+ if (j >= 0) {
+ cpr3_err(vreg, "invalid fuse corner mapping\n");
+ rc = -EINVAL;
+ goto done;
+ }
+
+ /*
+ * Interpolation is not possible for corners mapped to the lowest fuse
+ * corner so use the fuse corner value directly.
+ */
+ i = CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS;
+ quot_adjust = cpr3_quot_adjustment(ro_scale[i], volt_adjust_fuse[i]);
+ quot = fuse->target_quot[i] + quot_adjust;
+ quot_high[i] = quot_low[i] = quot;
+ ro = fuse->ro_sel[i];
+ if (quot_adjust)
+ cpr3_debug(vreg, "adjusted fuse corner %d RO%u target quot: %llu --> %u (%d uV)\n",
+ i, ro, fuse->target_quot[i], quot, volt_adjust_fuse[i]);
+
+ for (i = 0; i <= fmax_corner[CPRH_MSMCOBALT_KBSS_FUSE_CORNER_LOWSVS];
+ i++)
+ vreg->corner[i].target_quot[ro] = quot;
+
+ for (i = CPRH_MSMCOBALT_KBSS_FUSE_CORNER_SVS;
+ i < vreg->fuse_corner_count; i++) {
+ quot_high[i] = fuse->target_quot[i];
+ if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
+ quot_low[i] = quot_high[i - 1];
+ else
+ quot_low[i] = quot_high[i]
+ - fuse->quot_offset[i]
+ * MSMCOBALT_KBSS_QUOT_OFFSET_SCALE;
+ if (quot_high[i] < quot_low[i]) {
+ cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu; overriding: quot_high[%d]=%llu\n",
+ i, quot_high[i], i, quot_low[i],
+ i, quot_low[i]);
+ quot_high[i] = quot_low[i];
+ }
+ }
+
+ /* Perform per-fuse-corner target quotient adjustment */
+ for (i = 1; i < vreg->fuse_corner_count; i++) {
+ quot_adjust = cpr3_quot_adjustment(ro_scale[i],
+ volt_adjust_fuse[i]);
+ if (quot_adjust) {
+ prev_quot = quot_high[i];
+ quot_high[i] += quot_adjust;
+ cpr3_debug(vreg, "adjusted fuse corner %d RO%llu target quot: %llu --> %llu (%d uV)\n",
+ i, fuse->ro_sel[i], prev_quot, quot_high[i],
+ volt_adjust_fuse[i]);
+ }
+
+ if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
+ quot_low[i] = quot_high[i - 1];
+ else
+ quot_low[i] += cpr3_quot_adjustment(ro_scale[i],
+ volt_adjust_fuse[i - 1]);
+
+ if (quot_high[i] < quot_low[i]) {
+ cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu after adjustment; overriding: quot_high[%d]=%llu\n",
+ i, quot_high[i], i, quot_low[i],
+ i, quot_low[i]);
+ quot_high[i] = quot_low[i];
+ }
+ }
+
+ /* Interpolate voltages for the higher fuse corners. */
+ for (i = 1; i < vreg->fuse_corner_count; i++) {
+ freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
+ freq_high = vreg->corner[fmax_corner[i]].proc_freq;
+
+ ro = fuse->ro_sel[i];
+ for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
+ vreg->corner[j].target_quot[ro] = cpr3_interpolate(
+ freq_low, quot_low[i], freq_high, quot_high[i],
+ vreg->corner[j].proc_freq);
+ }
+
+ /* Perform per-corner target quotient adjustment */
+ for (i = 0; i < vreg->corner_count; i++) {
+ fuse_corner = vreg->corner[i].cpr_fuse_corner;
+ ro = fuse->ro_sel[fuse_corner];
+ quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
+ volt_adjust[i]);
+ if (quot_adjust) {
+ prev_quot = vreg->corner[i].target_quot[ro];
+ vreg->corner[i].target_quot[ro] += quot_adjust;
+ cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %llu --> %u (%d uV)\n",
+ i, ro, prev_quot,
+ vreg->corner[i].target_quot[ro],
+ volt_adjust[i]);
+ }
+ }
+
+ /* Ensure that target quotients increase monotonically */
+ for (i = 1; i < vreg->corner_count; i++) {
+ ro = fuse->ro_sel[vreg->corner[i].cpr_fuse_corner];
+ if (fuse->ro_sel[vreg->corner[i - 1].cpr_fuse_corner] == ro
+ && vreg->corner[i].target_quot[ro]
+ < vreg->corner[i - 1].target_quot[ro]) {
+ cpr3_debug(vreg, "adjusted corner %d RO%u target quot=%u < adjusted corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
+ i, ro, vreg->corner[i].target_quot[ro],
+ i - 1, ro, vreg->corner[i - 1].target_quot[ro],
+ i, ro, vreg->corner[i - 1].target_quot[ro]);
+ vreg->corner[i].target_quot[ro]
+ = vreg->corner[i - 1].target_quot[ro];
+ }
+ }
+
+done:
+ kfree(volt_adjust);
+ kfree(volt_adjust_fuse);
+ kfree(ro_scale);
+ kfree(fmax_corner);
+ kfree(quot_low);
+ kfree(quot_high);
+ return rc;
+}
+
+/**
+ * cprh_kbss_print_settings() - print out KBSS CPR configuration settings into
+ * the kernel log for debugging purposes
+ * @vreg: Pointer to the CPR3 regulator
+ */
+static void cprh_kbss_print_settings(struct cpr3_regulator *vreg)
+{
+ struct cpr3_corner *corner;
+ int i;
+
+ cpr3_debug(vreg, "Corner: Frequency (Hz), Fuse Corner, Floor (uV), Open-Loop (uV), Ceiling (uV)\n");
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+ cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
+ i, corner->proc_freq, corner->cpr_fuse_corner,
+ corner->floor_volt, corner->open_loop_volt,
+ corner->ceiling_volt);
+ }
+}
+
+/**
+ * cprh_kbss_init_thread() - perform steps necessary to initialize the
+ * configuration data for a CPR3 thread
+ * @thread: Pointer to the CPR3 thread
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_init_thread(struct cpr3_thread *thread)
+{
+ int rc;
+
+ rc = cpr3_parse_common_thread_data(thread);
+ if (rc) {
+ cpr3_err(thread->ctrl, "thread %u unable to read CPR thread data from device tree, rc=%d\n",
+ thread->thread_id, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_init_regulator() - perform all steps necessary to initialize the
+ * configuration data for a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_init_regulator(struct cpr3_regulator *vreg)
+{
+ struct cprh_msmcobalt_kbss_fuses *fuse;
+ int rc;
+
+ rc = cprh_msmcobalt_kbss_read_fuse_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
+ return rc;
+ }
+
+ fuse = vreg->platform_fuses;
+
+ rc = cprh_kbss_parse_corner_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR corner data from device tree, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_msmcobalt_kbss_calculate_open_loop_voltages(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to calculate open-loop voltages, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_limit_open_loop_voltages(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_adjust_voltages_for_apm(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to adjust voltages for APM\n, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cpr3_open_loop_voltage_as_ceiling(vreg);
+
+ rc = cpr3_limit_floor_voltages(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cprh_msmcobalt_kbss_calculate_target_quotients(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_parse_core_count_temp_adj_properties(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse core count and temperature adjustment properties, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr4_parse_core_count_temp_voltage_adj(vreg, true);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse temperature and core count voltage adjustments, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (vreg->allow_core_count_adj && (vreg->max_core_count <= 0
+ || vreg->max_core_count >
+ MSMCOBALT_KBSS_CPR_SDELTA_CORE_COUNT)) {
+ cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
+ vreg->max_core_count);
+ return -EINVAL;
+ }
+
+ rc = cprh_kbss_apm_threshold_as_corner(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to introduce APM voltage threshold corner\n, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cprh_kbss_print_settings(vreg);
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_init_controller() - perform KBSS CPRh controller specific
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_init_controller(struct cpr3_controller *ctrl)
+{
+ int rc;
+
+ rc = cpr3_parse_common_ctrl_data(ctrl);
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-controller-id",
+ &ctrl->ctrl_id);
+ if (rc) {
+ cpr3_err(ctrl, "could not read DT property qcom,cpr-controller-id, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (ctrl->ctrl_id < MSMCOBALT_KBSS_MIN_CONTROLLER_ID ||
+ ctrl->ctrl_id > MSMCOBALT_KBSS_MAX_CONTROLLER_ID) {
+ cpr3_err(ctrl, "invalid qcom,cpr-controller-id specified\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-down-error-step-limit",
+ &ctrl->down_error_step_limit);
+ if (rc) {
+ cpr3_err(ctrl, "error reading qcom,cpr-down-error-step-limit, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-up-error-step-limit",
+ &ctrl->up_error_step_limit);
+ if (rc) {
+ cpr3_err(ctrl, "error reading qcom,cpr-up-error-step-limit, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,voltage-base",
+ &ctrl->base_volt);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,voltage-base, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-up-down-delay-time",
+ &ctrl->up_down_delay_time);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,cpr-up-down-delay-time, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,apm-threshold-voltage",
+ &ctrl->apm_threshold_volt);
+ if (rc)
+ cpr3_debug(ctrl, "qcom,apm-threshold-voltage not specified\n");
+
+ ctrl->saw_use_unit_mV = of_property_read_bool(ctrl->dev->of_node,
+ "qcom,cpr-saw-use-unit-mV");
+
+ of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-voltage-settling-time",
+ &ctrl->voltage_settling_time);
+
+ of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-corner-switch-delay-time",
+ &ctrl->corner_switch_delay_time);
+
+ ctrl->sensor_count = ctrl->ctrl_id == MSMCOBALT_KBSS_POWER_CLUSTER_ID ?
+ MSMCOBALT_KBSS_POWER_CPR_SENSOR_COUNT :
+ MSMCOBALT_KBSS_PERFORMANCE_CPR_SENSOR_COUNT;
+
+ /*
+ * KBSS only has one thread (0) per controller so the zeroed
+ * array does not need further modification.
+ */
+ ctrl->sensor_owner = devm_kcalloc(ctrl->dev, ctrl->sensor_count,
+ sizeof(*ctrl->sensor_owner), GFP_KERNEL);
+ if (!ctrl->sensor_owner)
+ return -ENOMEM;
+
+ ctrl->cpr_clock_rate = MSMCOBALT_KBSS_CPR_CLOCK_RATE;
+ ctrl->ctrl_type = CPR_CTRL_TYPE_CPRH;
+ ctrl->supports_hw_closed_loop = true;
+ ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
+ "qcom,cpr-hw-closed-loop");
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_populate_opp_table() - populate an Operating Performance Point
+ * table with the frequencies associated with each corner.
+ * This table may be used to resolve corner to frequency to
+ * open-loop voltage mappings.
+ * @pdev: Pointer to the platform device
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_populate_opp_table(struct cpr3_controller *ctrl)
+{
+ struct device *dev = ctrl->dev;
+ struct cpr3_regulator *vreg = &ctrl->thread[0].vreg[0];
+ struct cpr3_corner *corner;
+ int rc, i;
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+ rc = dev_pm_opp_add(dev, corner->proc_freq, i + 1);
+ if (rc) {
+ cpr3_err(ctrl, "could not add OPP for corner %d with frequency %u MHz, rc=%d\n",
+ i + 1, corner->proc_freq, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int cprh_kbss_regulator_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_suspend(ctrl);
+}
+
+static int cprh_kbss_regulator_resume(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_resume(ctrl);
+}
+
+static struct of_device_id cprh_regulator_match_table[] = {
+ { .compatible = "qcom,cprh-msmcobalt-kbss-regulator", },
+ {}
+};
+
+static int cprh_kbss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cpr3_controller *ctrl;
+ int rc;
+
+ if (!dev->of_node) {
+ dev_err(dev, "Device tree node is missing\n");
+ return -EINVAL;
+ }
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->dev = dev;
+ ctrl->cpr_allowed_hw = true;
+
+ rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
+ &ctrl->name);
+ if (rc) {
+ cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_map_fuse_base(ctrl, pdev);
+ if (rc) {
+ cpr3_err(ctrl, "could not map fuse base address\n");
+ return rc;
+ }
+
+ rc = cpr3_allocate_threads(ctrl, 0, 0);
+ if (rc) {
+ cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (ctrl->thread_count != 1) {
+ cpr3_err(ctrl, "expected 1 thread but found %d\n",
+ ctrl->thread_count);
+ return -EINVAL;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ rc = cprh_kbss_init_controller(ctrl);
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_init_thread(&ctrl->thread[0]);
+ if (rc) {
+ cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_init_regulator(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(&ctrl->thread[0].vreg[0], "regulator initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ rc = cprh_kbss_populate_opp_table(ctrl);
+ if (rc)
+ panic("cprh-kbss-regulator OPP table initialization failed\n");
+
+ return cpr3_regulator_register(pdev, ctrl);
+}
+
+static int cprh_kbss_regulator_remove(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_unregister(ctrl);
+}
+
+static struct platform_driver cprh_kbss_regulator_driver = {
+ .driver = {
+ .name = "qcom,cprh-kbss-regulator",
+ .of_match_table = cprh_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = cprh_kbss_regulator_probe,
+ .remove = cprh_kbss_regulator_remove,
+ .suspend = cprh_kbss_regulator_suspend,
+ .resume = cprh_kbss_regulator_resume,
+};
+
+static int cpr_regulator_init(void)
+{
+ return platform_driver_register(&cprh_kbss_regulator_driver);
+}
+
+static void cpr_regulator_exit(void)
+{
+ platform_driver_unregister(&cprh_kbss_regulator_driver);
+}
+
+MODULE_DESCRIPTION("CPRh KBSS regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);