diff --git a/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt new file mode 100644 index 000000000000..41cec67b7627 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt @@ -0,0 +1,321 @@ +Qualcomm Technologies, Inc. CPR4 Regulator - MMSS LDO Specific Bindings + +MMSS LDO CPR4 controllers each support one CPR thread that monitors the voltage +of the graphics processor (MMSS) supply regulator. The CPR open-loop voltages +are stored in hardware fuses for MMSS CPR4 controllers. However, the CPR target +quotients must be defined in device tree. + +This document describes the MMSS LDO specific CPR4 bindings. + +======================= +Required Node Structure +======================= + +CPR3 regulators must be described in three levels of devices nodes. The first +level describes the CPR3 controller. The second level describes exacly one +hardware thread managed by the controller. The third level describes one or +more logical regulators handled by the CPR thread. + +All platform independent cpr3-regulator binding guidelines defined in +cpr3-regulator.txt also apply to cpr4-mmss-ldo-regulator devices. + +==================================== +First Level Nodes - CPR3 Controllers +==================================== + +MMSS LDO specific properties: +- compatible + Usage: required + Value type: + Definition: should be the following: + "qcom,cpr4-msmfalcon-mmss-ldo-regulator". + +- clocks + Usage: required + Value type: + Definition: Array of clock tuples in which each tuple consists of a + phandle to a clock device and a clock ID number. The + following clocks must be specified: MMSS RBCPR and MMSS + RBCPR AHB. + +- clock-names + Usage: required + Value type: + Definition: Clock names. This list must match up 1-to-1 with the clocks + specified in the 'clocks' property. "core_clk", and "bus_clk" + must be specified. + +- qcom,cpr-step-quot-fixed + Usage: Optional + Value type: + Definition: Fixed step quotient value used by controller for applying + the SDELTA margin adjustments on the programmed target + quotient values. The step quotient is the number of + additional ring oscillator ticks observed for each + qcom,voltage-step increase in vdd-supply output voltage. + Supported values: 0 - 63. + +================================================= +Second Level Nodes - CPR Threads for a Controller +================================================= + +MMSS specific properties: +N/A + +=============================================== +Third Level Nodes - CPR Regulators for a Thread +=============================================== + +MMSS specific properties: +- qcom,cpr-fuse-corners + Usage: required + Value type: + Definition: Specifies the number of fuse corners. This value must be 6 + for msmfalcon GFX LDO. These fuse corners are: MinSVS, + LowSVS, SVS, SVSP, NOM and NOMP. The open-loop voltage fuses + are allocated for LowSVS, SVS, NOM and NOMP corners. The + open-loop voltages for MinSVS and SVSP are derived by + applying fixed offset from LowSVS and NOM open-loop voltages + respectively. The closed-loop offset voltage fuses are + allocated for LowSVS, SVS, NOM and NOMP corners. The MinSVS + and SVSP corners use the closed-loop offset voltage fuses of + LowSVS and NOM corners respectively. + +- qcom,cpr-fuse-combos + Usage: required + Value type: + 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 MMSS the maximum + supported value is 8. These combos correspond to CPR + revision fuse values from 0 to 7 in order. + +- qcom,mem-acc-voltage + Usage: required if mem-acc-supply is specified for the CPR3 controller + containing this CPR3 regulator + Value type: + Definition: A list of integer tuples which each define the mem-acc-supply + corner for each voltage corner in order from lowest to highest. + + 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-corners property or + the qcom,cpr-speed-bins property. A single tuple may only + be specified if all of the corner counts in qcom,cpr-corners + are the same. + +- qcom,cpr-target-quotients + Usage: required + Value type: + Definition: A grouping of integer tuple lists. Each tuple defines the + CPR target quotient for each ring oscillator (RO) for a + given corner. Since CPR3 supports exactly 16 ROs, each + tuple must contain 16 elements corresponding to RO0 through + RO15 in order. If a given RO is unused for a corner, then + its target quotient should be specified as 0. + + Each tuple list in the grouping must meet the same size + requirements as those specified for qcom,mem-acc-voltage + above. The tuples in a given list are ordered from the + lowest corner to the highest corner. + +- qcom,cpr-ro-scaling-factor + Usage: required if qcom,cpr-closed-loop-voltage-adjustment is + specified + Value type: + Definition: The common definition of this property in cpr3-regulator.txt + is accurate for MMSS CPR3 controllers except for this + modification: + + Each tuple list must contain the number of tuples defined in + the corresponding element of the qcom,cpr-corners property + or the qcom,cpr-speed-bins property as opposed to the value + of the qcom,cpr-fuse-corners property. + +- qcom,cpr-fused-closed-loop-voltage-adjustment-map + Usage: optional + Value type: + Definition: A list of integer tuples which each define the CPR fused + corner closed-loop offset adjustment fuse to utilize for + each voltage corner in order from lowest to highest. + + 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-corners property or + the qcom,cpr-speed-bins property. A single tuple may only + be specified if all of the corner counts in qcom,cpr-corners + are the same. + + Each tuple element must be either 0 or in the range 1 to + qcom,cpr-fuse-corners. A value of 0 signifies that no fuse + based adjustment should be applied to the fuse corner. + Values 1 to qcom,cpr-fuse-corners denote the specific fuse + corner that should be used by a given voltage corner. + +- qcom,cpr-corner-allow-ldo-mode + Usage: optional + Value type: + Definition: A list of integer tuples which each define the LDO mode + allowed state for each voltage corner in order from lowest + to highest. Each element in the tuple should be either + 0 (LDO mode not allowed) or 1 (LDO mode allowed). + + 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-corners property or + the qcom,cpr-speed-bin-corners property. A single tuple may + only be specified if all of the corner counts in + qcom,cpr-corners are the same. + +- qcom,cpr-corner-allow-closed-loop + Usage: optional + Value type: + Definition: A list of integer tuples which each define the CPR + closed-loop operation allowed state for each voltage corner + in order from lowest to highest. Each element in the tuple + should be either 0 (CPR closed-loop operation not allowed) + or 1 (CPR closed-loop operation allowed). + + 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-corners property or + the qcom,cpr-speed-bin-corners property. A single tuple may + only be specified if all of the corner counts in + qcom,cpr-corners are the same. + +Note that the qcom,cpr-closed-loop-voltage-fuse-adjustment property is not +meaningful for MMSS LDO CPR3 regulator nodes since target quotients are not +defined in fuses. + +======= +Example +======= + +gfx_cpr: cpr4-ctrl@05061000 { + compatible = "qcom,cpr4-msmfalcon-mmss-ldo-regulator"; + reg = <0x05061000 0x4000>, <0x00784000 0x1000>; + reg-names = "cpr_ctrl", "fuse_base"; + interrupts = ; + interrupt-names = "cpr"; + qcom,cpr-ctrl-name = "gfx"; + + qcom,cpr-sensor-time = <1000>; + qcom,cpr-loop-time = <5000000>; + qcom,cpr-idle-cycles = <15>; + qcom,cpr-step-quot-init-min = <8>; + qcom,cpr-step-quot-init-max = <12>; + qcom,cpr-count-mode = <0>; /* All at once */ + + vdd-supply = <&gfx_stub_vreg>; + mem-acc-supply = <&gfx_mem_acc_vreg>; + system-supply = <&pm2falcon_s3_level>; /* vdd_cx */ + qcom,voltage-step = <5000>; + vdd-thread0-ldo-supply = <&gfx_ldo_vreg>; + + qcom,cpr-enable; + + thread@0 { + qcom,cpr-thread-id = <0>; + qcom,cpr-consecutive-up = <0>; + qcom,cpr-consecutive-down = <2>; + qcom,cpr-up-threshold = <0>; + qcom,cpr-down-threshold = <2>; + + gfx_vreg_corner: regulator { + regulator-name = "gfx_corner"; + regulator-min-microvolt = <1>; + regulator-max-microvolt = <7>; + + qcom,cpr-fuse-corners = <6>; + qcom,cpr-fuse-combos = <8>; + qcom,cpr-corners = <7>; + + qcom,cpr-corner-fmax-map = <1 2 3 4 5 6>; + + qcom,cpr-voltage-ceiling = + <584000 644000 724000 788000 + 868000 924000 1068000>; + qcom,cpr-voltage-floor = + <504000 504000 596000 652000 + 712000 744000 1068000>; + + qcom,mem-acc-voltage = <1 1 1 2 2 2 2>; + qcom,system-voltage = + , + , + , + , + , + , + ; + + qcom,corner-frequencies = + <160000000 266000000 370000000 + 465000000 588000000 647000000 + 800000000>; + + qcom,cpr-target-quotients = + <0 0 0 0 0 0 185 179 + 291 299 304 319 0 0 0 0>, + <0 0 0 0 0 0 287 273 + 425 426 443 453 0 0 0 0>, + <0 0 0 0 0 0 414 392 + 584 576 608 612 0 0 0 0>, + <0 0 0 0 0 0 459 431 + 684 644 692 679 0 0 0 0>, + <0 0 0 0 0 0 577 543 + 798 768 823 810 0 0 0 0>, + <0 0 0 0 0 0 669 629 + 886 864 924 911 0 0 0 0>, + <0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0>; + + qcom,cpr-ro-scaling-factor = + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 2035 1917 + 1959 2131 2246 2253 0 0 0 0>, + < 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0>; + + qcom,cpr-scaled-open-loop-voltage-as-ceiling; + qcom,cpr-corner-ldo-mode-allowed = + <1 1 1 1 1 1 0>; + qcom,cpr-corner-use-closed-loop = + <1 1 1 1 1 1 0>; + }; + }; +}; diff --git a/arch/arm/configs/msmfalcon-perf_defconfig b/arch/arm/configs/msmfalcon-perf_defconfig index 37b44ff5358f..211ec1c6cabc 100644 --- a/arch/arm/configs/msmfalcon-perf_defconfig +++ b/arch/arm/configs/msmfalcon-perf_defconfig @@ -348,6 +348,7 @@ CONFIG_WCD9335_CODEC=y CONFIG_WCD934X_CODEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_MSM_GFX_LDO=y CONFIG_REGULATOR_RPM_SMD=y CONFIG_REGULATOR_QPNP=y CONFIG_REGULATOR_QPNP_LABIBB=y @@ -355,6 +356,7 @@ CONFIG_REGULATOR_SPM=y CONFIG_REGULATOR_CPR3_HMSS=y CONFIG_REGULATOR_CPR3_MMSS=y CONFIG_REGULATOR_CPRH_KBSS=y +CONFIG_REGULATOR_CPR4_MMSS_LDO=y CONFIG_REGULATOR_MEM_ACC=y CONFIG_REGULATOR_PROXY_CONSUMER=y CONFIG_REGULATOR_STUB=y diff --git a/arch/arm/configs/msmfalcon_defconfig b/arch/arm/configs/msmfalcon_defconfig index ae0ccfe902c9..10981157afc3 100644 --- a/arch/arm/configs/msmfalcon_defconfig +++ b/arch/arm/configs/msmfalcon_defconfig @@ -346,6 +346,7 @@ CONFIG_WCD9335_CODEC=y CONFIG_WCD934X_CODEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_MSM_GFX_LDO=y CONFIG_REGULATOR_RPM_SMD=y CONFIG_REGULATOR_QPNP=y CONFIG_REGULATOR_QPNP_LABIBB=y @@ -354,6 +355,7 @@ CONFIG_REGULATOR_SPM=y CONFIG_REGULATOR_CPR3_HMSS=y CONFIG_REGULATOR_CPR3_MMSS=y CONFIG_REGULATOR_CPRH_KBSS=y +CONFIG_REGULATOR_CPR4_MMSS_LDO=y CONFIG_REGULATOR_MEM_ACC=y CONFIG_REGULATOR_PROXY_CONSUMER=y CONFIG_REGULATOR_STUB=y diff --git a/arch/arm64/configs/msmfalcon-perf_defconfig b/arch/arm64/configs/msmfalcon-perf_defconfig index 56f31336128c..b84168e18478 100644 --- a/arch/arm64/configs/msmfalcon-perf_defconfig +++ b/arch/arm64/configs/msmfalcon-perf_defconfig @@ -346,6 +346,7 @@ CONFIG_WCD9335_CODEC=y CONFIG_WCD934X_CODEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_MSM_GFX_LDO=y CONFIG_REGULATOR_RPM_SMD=y CONFIG_REGULATOR_QPNP=y CONFIG_REGULATOR_QPNP_LABIBB=y @@ -354,6 +355,7 @@ CONFIG_REGULATOR_SPM=y CONFIG_REGULATOR_CPR3_HMSS=y CONFIG_REGULATOR_CPR3_MMSS=y CONFIG_REGULATOR_CPRH_KBSS=y +CONFIG_REGULATOR_CPR4_MMSS_LDO=y CONFIG_REGULATOR_MEM_ACC=y CONFIG_REGULATOR_PROXY_CONSUMER=y CONFIG_REGULATOR_STUB=y diff --git a/arch/arm64/configs/msmfalcon_defconfig b/arch/arm64/configs/msmfalcon_defconfig index 431a474e7694..ce91e8992b46 100644 --- a/arch/arm64/configs/msmfalcon_defconfig +++ b/arch/arm64/configs/msmfalcon_defconfig @@ -348,6 +348,7 @@ CONFIG_WCD9335_CODEC=y CONFIG_WCD934X_CODEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_MSM_GFX_LDO=y CONFIG_REGULATOR_RPM_SMD=y CONFIG_REGULATOR_QPNP=y CONFIG_REGULATOR_QPNP_LABIBB=y @@ -356,6 +357,7 @@ CONFIG_REGULATOR_SPM=y CONFIG_REGULATOR_CPR3_HMSS=y CONFIG_REGULATOR_CPR3_MMSS=y CONFIG_REGULATOR_CPRH_KBSS=y +CONFIG_REGULATOR_CPR4_MMSS_LDO=y CONFIG_REGULATOR_MEM_ACC=y CONFIG_REGULATOR_PROXY_CONSUMER=y CONFIG_REGULATOR_STUB=y diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index ba1bf3b5f492..bd2c1a8e7540 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -925,6 +925,17 @@ config REGULATOR_CPRH_KBSS independent voltage supplies. This driver reads both initial voltage and CPR target quotient values out of hardware fuses. +config REGULATOR_CPR4_MMSS_LDO + bool "RBCPR3 regulator for MMSS LDO" + depends on OF + select REGULATOR_CPR3 + help + This driver supports Qualcomm Technologies, Inc. MMSS graphics + processor specific features. The MMSS CPR3 controller only uses one + thread to monitor the MMSS LDO voltage requirements. This driver reads + initial voltage values out of hardware fuses and CPR target quotient + values out of device tree. + config REGULATOR_KRYO bool "Kryo regulator driver" depends on OF diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 79a203418a0b..abd116d3d8af 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -114,6 +114,7 @@ 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_CPR4_MMSS_LDO) += cpr4-mmss-ldo-regulator.o obj-$(CONFIG_REGULATOR_QPNP_LABIBB) += qpnp-labibb-regulator.o obj-$(CONFIG_REGULATOR_QPNP_LCDB) += qpnp-lcdb-regulator.o obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o diff --git a/drivers/regulator/cpr4-mmss-ldo-regulator.c b/drivers/regulator/cpr4-mmss-ldo-regulator.c new file mode 100644 index 000000000000..9fa5c309b02a --- /dev/null +++ b/drivers/regulator/cpr4-mmss-ldo-regulator.c @@ -0,0 +1,722 @@ +/* + * Copyright (c) 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpr3-regulator.h" + +#define MSMFALCON_MMSS_FUSE_CORNERS 6 + +/** + * struct cpr4_msmfalcon_mmss_fuses - MMSS specific fuse data for MSMFALCON + * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value + * for each fuse corner (raw, not converted to a voltage) + * @offset_voltage: The closed-loop voltage margin adjustment fuse parameter + * value for each fuse corner (raw, not converted to a + * voltage) + * @cpr_fusing_rev: CPR fusing revision fuse parameter value + * @ldo_enable: The ldo enable fuse parameter for each fuse corner + * indicates that VDD_GFX can be configured to LDO mode in + * the corresponding fuse corner. + * @ldo_cpr_cl_enable: A fuse parameter indicates that GFX CPR can be + * configured to operate in closed-loop mode when VDD_GFX + * is configured for LDO sub-regulated mode. + * + * This struct holds the values for all of the fuses read from memory. + */ +struct cpr4_msmfalcon_mmss_fuses { + u64 init_voltage[MSMFALCON_MMSS_FUSE_CORNERS]; + u64 offset_voltage[MSMFALCON_MMSS_FUSE_CORNERS]; + u64 cpr_fusing_rev; + u64 ldo_enable[MSMFALCON_MMSS_FUSE_CORNERS]; + u64 ldo_cpr_cl_enable; +}; + +/* Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 */ +#define CPR4_MSMFALCON_MMSS_FUSE_COMBO_COUNT 8 + +/* + * MSMFALCON MMSS fuse parameter locations: + * + * Structs are organized with the following dimensions: + * Outer: 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 +msmfalcon_mmss_init_voltage_param[MSMFALCON_MMSS_FUSE_CORNERS][2] = { + {{65, 39, 43}, {} }, + {{65, 39, 43}, {} }, + {{65, 34, 38}, {} }, + {{65, 34, 38}, {} }, + {{65, 29, 33}, {} }, + {{65, 24, 28}, {} }, +}; + +static const struct cpr3_fuse_param msmfalcon_cpr_fusing_rev_param[] = { + {71, 34, 36}, + {}, +}; + +static const struct cpr3_fuse_param +msmfalcon_mmss_offset_voltage_param[MSMFALCON_MMSS_FUSE_CORNERS][2] = { + {{} }, + {{} }, + {{} }, + {{65, 52, 55}, {} }, + {{65, 48, 51}, {} }, + {{65, 44, 47}, {} }, +}; + +static const struct cpr3_fuse_param +msmfalcon_mmss_ldo_enable_param[MSMFALCON_MMSS_FUSE_CORNERS][2] = { + {{73, 62, 62}, {} }, + {{73, 61, 61}, {} }, + {{73, 60, 60}, {} }, + {{73, 59, 59}, {} }, + {{73, 58, 58}, {} }, + {{73, 57, 57}, {} }, +}; + +static const struct cpr3_fuse_param msmfalcon_ldo_cpr_cl_enable_param[] = { + {71, 38, 38}, + {}, +}; + +/* Additional MSMFALCON specific data: */ + +/* Open loop voltage fuse reference voltages in microvolts */ +static const int msmfalcon_mmss_fuse_ref_volt[MSMFALCON_MMSS_FUSE_CORNERS] = { + 584000, + 644000, + 724000, + 788000, + 868000, + 924000, +}; + +#define MSMFALCON_MMSS_FUSE_STEP_VOLT 10000 +#define MSMFALCON_MMSS_OFFSET_FUSE_STEP_VOLT 10000 +#define MSMFALCON_MMSS_VOLTAGE_FUSE_SIZE 5 + +#define MSMFALCON_MMSS_CPR_SENSOR_COUNT 11 + +#define MSMFALCON_MMSS_CPR_CLOCK_RATE 19200000 + +/** + * cpr4_msmfalcon_mmss_read_fuse_data() - load MMSS specific fuse parameter + * values + * @vreg: Pointer to the CPR3 regulator + * + * This function allocates a cpr4_msmfalcon_mmss_fuses struct, fills it with + * values read out of hardware fuses, and finally copies common fuse values + * into the regulator struct. + * + * Return: 0 on success, errno on failure + */ +static int cpr4_msmfalcon_mmss_read_fuse_data(struct cpr3_regulator *vreg) +{ + void __iomem *base = vreg->thread->ctrl->fuse_base; + struct cpr4_msmfalcon_mmss_fuses *fuse; + int i, rc; + + fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL); + if (!fuse) + return -ENOMEM; + + rc = cpr3_read_fuse_param(base, msmfalcon_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); + + rc = cpr3_read_fuse_param(base, msmfalcon_ldo_cpr_cl_enable_param, + &fuse->ldo_cpr_cl_enable); + if (rc) { + cpr3_err(vreg, "Unable to read ldo cpr closed-loop enable fuse, rc=%d\n", + rc); + return rc; + } + + for (i = 0; i < MSMFALCON_MMSS_FUSE_CORNERS; i++) { + rc = cpr3_read_fuse_param(base, + msmfalcon_mmss_init_voltage_param[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, + msmfalcon_mmss_offset_voltage_param[i], + &fuse->offset_voltage[i]); + if (rc) { + cpr3_err(vreg, "Unable to read fuse-corner %d offset voltage fuse, rc=%d\n", + i, rc); + return rc; + } + + rc = cpr3_read_fuse_param(base, + msmfalcon_mmss_ldo_enable_param[i], + &fuse->ldo_enable[i]); + if (rc) { + cpr3_err(vreg, "Unable to read fuse-corner %d ldo enable fuse, rc=%d\n", + i, rc); + return rc; + } + } + + vreg->fuse_combo = fuse->cpr_fusing_rev; + if (vreg->fuse_combo >= CPR4_MSMFALCON_MMSS_FUSE_COMBO_COUNT) { + cpr3_err(vreg, "invalid CPR fuse combo = %d found, not in range 0 - %d\n", + vreg->fuse_combo, + CPR4_MSMFALCON_MMSS_FUSE_COMBO_COUNT - 1); + return -EINVAL; + } + + vreg->cpr_rev_fuse = fuse->cpr_fusing_rev; + vreg->fuse_corner_count = MSMFALCON_MMSS_FUSE_CORNERS; + vreg->platform_fuses = fuse; + + return 0; +} + +/** + * cpr3_msmfalcon_mmss_calculate_open_loop_voltages() - calculate the open-loop + * voltage for each corner of a CPR3 regulator + * @vreg: Pointer to the CPR3 regulator + * + * Return: 0 on success, errno on failure + */ +static int cpr4_msmfalcon_mmss_calculate_open_loop_voltages( + struct cpr3_regulator *vreg) +{ + struct cpr4_msmfalcon_mmss_fuses *fuse = vreg->platform_fuses; + int i, rc = 0; + const int *ref_volt; + int *fuse_volt; + + fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt), + GFP_KERNEL); + if (!fuse_volt) + return -ENOMEM; + + ref_volt = msmfalcon_mmss_fuse_ref_volt; + for (i = 0; i < vreg->fuse_corner_count; i++) { + fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(ref_volt[i], + MSMFALCON_MMSS_FUSE_STEP_VOLT, fuse->init_voltage[i], + MSMFALCON_MMSS_VOLTAGE_FUSE_SIZE); + cpr3_info(vreg, "fuse_corner[%d] open-loop=%7d uV\n", + 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; + } + + for (i = 1; i < vreg->fuse_corner_count; i++) { + if (fuse_volt[i] < fuse_volt[i - 1]) { + cpr3_debug(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]; + } + } + + for (i = 0; i < vreg->corner_count; i++) + vreg->corner[i].open_loop_volt + = fuse_volt[vreg->corner[i].cpr_fuse_corner]; + + 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); + +done: + kfree(fuse_volt); + return rc; +} + +/** + * cpr4_mmss_parse_ldo_mode_data() - Parse the LDO mode enable state for each + * corner of a CPR3 regulator + * @vreg: Pointer to the CPR3 regulator + * + * This function considers 2 sets of data: one set from device node and other + * set from fuses and applies set intersection to decide the final LDO mode + * enable state of each corner. If the device node configuration is not + * specified, then the function applies LDO mode disable for all corners. + * + * Return: 0 on success, errno on failure + */ +static int cpr4_mmss_parse_ldo_mode_data(struct cpr3_regulator *vreg) +{ + struct cpr4_msmfalcon_mmss_fuses *fuse = vreg->platform_fuses; + int i, rc = 0; + u32 *ldo_allowed; + char *prop_str = "qcom,cpr-corner-allow-ldo-mode"; + + if (!of_find_property(vreg->of_node, prop_str, NULL)) { + cpr3_debug(vreg, "%s property is missing. LDO mode is disabled for all corners\n", + prop_str); + return 0; + } + + ldo_allowed = kcalloc(vreg->corner_count, sizeof(*ldo_allowed), + GFP_KERNEL); + if (!ldo_allowed) + return -ENOMEM; + + rc = cpr3_parse_corner_array_property(vreg, prop_str, 1, ldo_allowed); + if (rc) { + cpr3_err(vreg, "%s read failed, rc=%d\n", prop_str, rc); + goto done; + } + + for (i = 0; i < vreg->corner_count; i++) + vreg->corner[i].ldo_mode_allowed + = (ldo_allowed[i] && fuse->ldo_enable[i]); + +done: + kfree(ldo_allowed); + return rc; +} + +/** + * cpr4_mmss_parse_corner_operating_mode() - Parse the CPR closed-loop operation + * enable state for each corner of a CPR3 regulator + * @vreg: Pointer to the CPR3 regulator + * + * This function ensures that closed-loop operation is enabled only for LDO + * mode allowed corners. + * + * Return: 0 on success, errno on failure + */ +static int cpr4_mmss_parse_corner_operating_mode(struct cpr3_regulator *vreg) +{ + struct cpr4_msmfalcon_mmss_fuses *fuse = vreg->platform_fuses; + int i, rc = 0; + u32 *use_closed_loop; + char *prop_str = "qcom,cpr-corner-allow-closed-loop"; + + if (!of_find_property(vreg->of_node, prop_str, NULL)) { + cpr3_debug(vreg, "%s property is missing. Use open-loop for all corners\n", + prop_str); + for (i = 0; i < vreg->corner_count; i++) + vreg->corner[i].use_open_loop = true; + + return 0; + } + + use_closed_loop = kcalloc(vreg->corner_count, sizeof(*use_closed_loop), + GFP_KERNEL); + if (!use_closed_loop) + return -ENOMEM; + + rc = cpr3_parse_corner_array_property(vreg, prop_str, 1, + use_closed_loop); + if (rc) { + cpr3_err(vreg, "%s read failed, rc=%d\n", prop_str, rc); + goto done; + } + + for (i = 0; i < vreg->corner_count; i++) + vreg->corner[i].use_open_loop + = !(fuse->ldo_cpr_cl_enable && use_closed_loop[i] + && vreg->corner[i].ldo_mode_allowed); + +done: + kfree(use_closed_loop); + return rc; +} + +/** + * cpr4_mmss_parse_corner_data() - parse MMSS corner data from device tree + * properties of the regulator's device node + * @vreg: Pointer to the CPR3 regulator + * + * Return: 0 on success, errno on failure + */ +static int cpr4_mmss_parse_corner_data(struct cpr3_regulator *vreg) +{ + int i, rc; + u32 *temp; + + rc = cpr3_parse_common_corner_data(vreg); + if (rc) { + cpr3_err(vreg, "error reading corner data, rc=%d\n", rc); + return rc; + } + + temp = kcalloc(vreg->corner_count * CPR3_RO_COUNT, sizeof(*temp), + GFP_KERNEL); + if (!temp) + return -ENOMEM; + + rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-target-quotients", + CPR3_RO_COUNT, temp); + if (rc) { + cpr3_err(vreg, "could not load target quotients, rc=%d\n", rc); + goto done; + } + + for (i = 0; i < vreg->corner_count; i++) + memcpy(vreg->corner[i].target_quot, &temp[i * CPR3_RO_COUNT], + sizeof(*temp) * CPR3_RO_COUNT); + +done: + kfree(temp); + return rc; +} + +/** + * cpr4_mmss_print_settings() - print out MMSS CPR configuration settings into + * the kernel log for debugging purposes + * @vreg: Pointer to the CPR3 regulator + */ +static void cpr4_mmss_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); + } +} + +/** + * cpr4_mmss_init_thread() - perform all 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 cpr4_mmss_init_thread(struct cpr3_thread *thread) +{ + struct cpr3_controller *ctrl = thread->ctrl; + struct cpr3_regulator *vreg = &thread->vreg[0]; + int rc; + + rc = cpr3_parse_common_thread_data(thread); + if (rc) { + cpr3_err(vreg, "unable to read CPR thread data from device tree, rc=%d\n", + rc); + return rc; + } + + if (!of_find_property(ctrl->dev->of_node, "vdd-thread0-ldo-supply", + NULL)) { + cpr3_err(vreg, "ldo supply regulator is not specified\n"); + return -EINVAL; + } + + vreg->ldo_regulator = devm_regulator_get(ctrl->dev, "vdd-thread0-ldo"); + if (IS_ERR(vreg->ldo_regulator)) { + rc = PTR_ERR(vreg->ldo_regulator); + if (rc != -EPROBE_DEFER) + cpr3_err(vreg, "unable to request vdd-thread0-ldo regulator, rc=%d\n", + rc); + return rc; + } + + vreg->ldo_mode_allowed = !of_property_read_bool(vreg->of_node, + "qcom,ldo-disable"); + vreg->ldo_regulator_bypass = BHS_MODE; + vreg->ldo_type = CPR3_LDO300; + + rc = cpr4_msmfalcon_mmss_read_fuse_data(vreg); + if (rc) { + cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc); + return rc; + } + + rc = cpr4_mmss_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 = cpr4_msmfalcon_mmss_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; + } + + 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 = cpr4_mmss_parse_ldo_mode_data(vreg); + if (rc) { + cpr3_err(vreg, "unable to parse ldo mode data, rc=%d\n", rc); + return rc; + } + + rc = cpr4_mmss_parse_corner_operating_mode(vreg); + if (rc) { + cpr3_err(vreg, "unable to parse closed-loop operating mode data, rc=%d\n", + rc); + return rc; + } + + cpr4_mmss_print_settings(vreg); + + return 0; +} + +/** + * cpr4_mmss_init_controller() - perform MMSS CPR4 controller specific + * initializations + * @ctrl: Pointer to the CPR3 controller + * + * Return: 0 on success, errno on failure + */ +static int cpr4_mmss_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; + } + + ctrl->sensor_count = MSMFALCON_MMSS_CPR_SENSOR_COUNT; + + /* + * MMSS only has one thread (0) 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 = MSMFALCON_MMSS_CPR_CLOCK_RATE; + ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4; + ctrl->support_ldo300_vreg = true; + + /* + * Use fixed step quotient if specified otherwise use dynamic + * calculated per RO step quotient + */ + of_property_read_u32(ctrl->dev->of_node, + "qcom,cpr-step-quot-fixed", + &ctrl->step_quot_fixed); + ctrl->use_dynamic_step_quot = !ctrl->step_quot_fixed; + + /* iface_clk is optional for msmfalcon */ + ctrl->iface_clk = NULL; + ctrl->bus_clk = devm_clk_get(ctrl->dev, "bus_clk"); + if (IS_ERR(ctrl->bus_clk)) { + rc = PTR_ERR(ctrl->bus_clk); + if (rc != -EPROBE_DEFER) + cpr3_err(ctrl, "unable request bus clock, rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int cpr4_mmss_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; + /* Set to false later if anything precludes CPR operation. */ + 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 = cpr4_mmss_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 = cpr4_mmss_init_thread(&ctrl->thread[0]); + if (rc) { + cpr3_err(&ctrl->thread[0].vreg[0], "thread initialization failed, rc=%d\n", + rc); + return rc; + } + + rc = cpr3_mem_acc_init(&ctrl->thread[0].vreg[0]); + if (rc) { + cpr3_err(ctrl, "failed to initialize mem-acc configuration, rc=%d\n", + rc); + return rc; + } + + platform_set_drvdata(pdev, ctrl); + + return cpr3_regulator_register(pdev, ctrl); +} + +static int cpr4_mmss_regulator_remove(struct platform_device *pdev) +{ + struct cpr3_controller *ctrl = platform_get_drvdata(pdev); + + return cpr3_regulator_unregister(ctrl); +} + +static int cpr4_mmss_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 cpr4_mmss_regulator_resume(struct platform_device *pdev) +{ + struct cpr3_controller *ctrl = platform_get_drvdata(pdev); + + return cpr3_regulator_resume(ctrl); +} + +/* Data corresponds to the SoC revision */ +static const struct of_device_id cpr4_mmss_regulator_match_table[] = { + { + .compatible = "qcom,cpr4-msmfalcon-mmss-ldo-regulator", + .data = (void *)NULL, + }, +}; + +static struct platform_driver cpr4_mmss_regulator_driver = { + .driver = { + .name = "qcom,cpr4-mmss-ldo-regulator", + .of_match_table = cpr4_mmss_regulator_match_table, + .owner = THIS_MODULE, + }, + .probe = cpr4_mmss_regulator_probe, + .remove = cpr4_mmss_regulator_remove, + .suspend = cpr4_mmss_regulator_suspend, + .resume = cpr4_mmss_regulator_resume, +}; + +static int cpr_regulator_init(void) +{ + return platform_driver_register(&cpr4_mmss_regulator_driver); +} + +static void cpr_regulator_exit(void) +{ + platform_driver_unregister(&cpr4_mmss_regulator_driver); +} + +MODULE_DESCRIPTION("CPR4 MMSS LDO regulator driver"); +MODULE_LICENSE("GPL v2"); + +arch_initcall(cpr_regulator_init); +module_exit(cpr_regulator_exit);