regulator: Changes for v4.19

The biggest set of changes in here is the addition of the Qualcomm RPMH
 driver.  As well as the regualtor driver itself being quite large due to
 the usual involved Qualcomm regulator stuff there's also some code
 shared with the arm-soc tree, a bus driver required to communicate with
 the hardware that actually winds up being much larger than the regulator
 driver itself and a LLCC driver that was part of the same signed tag
 used with the arm-soc tree.
 
 Other than that it's a fairly standard and quiet release, highlights
 include:
 
  - Addition of device links from regulator consumers to their
    regulators, helping the core avoid dependency issues during suspend.
  - Support for the entertainingly innovative suspend implementation in
    the BD9571MWV.
  - Support for switch regulators on the PFUZE100, this required two goes
    due to backwards compatibility issues with old DTs that were
    discovered.
  - Support for Freescale PFUZE3001 and SocioNext UniPhier.
  - The aforementioned Qualcomm RPMH driver together with the driver
    changes required to support it.
 -----BEGIN PGP SIGNATURE-----
 
 iQFHBAABCgAxFiEEreZoqmdXGLWf4p/qJNaLcl1Uh9AFAltxi/ATHGJyb29uaWVA
 a2VybmVsLm9yZwAKCRAk1otyXVSH0GNxB/99LLeB+gfD4ckoU07Az/k77+CfIZ8D
 HEUA9ryP4y6isUxpZMGcZhKa48ag9RxTuAGK+gGWtKUHxPIH6o0CuRLbGm3GOo99
 MTbbMaJRJIHkwsh8XjX2Adr02XSzU/9r2T3rpZD10T/J1Qj1D3W1YuvaE/fQuOII
 XW8QWhRuTWbVI/gSIEj+o2PcVK77yb9Nr+llNvS+BOlFAlnNo1tx3qCJ1bUiVEyD
 DhLkL0n0XfaOpbtZV5DIsVDiMsB+/TnXtqiijfvq0qi5SHh6jECpQYNWHBNyIkwj
 4N9NQ9xfC6bArAl9tWxXvJrVXUqFSRagHeB2xLD6piWcZ0DC4K0QfEW2
 =WiXl
 -----END PGP SIGNATURE-----

Merge tag 'regulator-v4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator

Pull regulator updates from Mark Brown:
 "The biggest set of changes in here is the addition of the Qualcomm
  RPMH driver. As well as the regualtor driver itself being quite large
  due to the usual involved Qualcomm regulator stuff there's also some
  code shared with the arm-soc tree, a bus driver required to
  communicate with the hardware that actually winds up being much larger
  than the regulator driver itself and a LLCC driver that was part of
  the same signed tag used with the arm-soc tree.

  Other than that it's a fairly standard and quiet release, highlights
  include:

   - Addition of device links from regulator consumers to their
     regulators, helping the core avoid dependency issues during
     suspend.

   - Support for the entertainingly innovative suspend implementation in
     the BD9571MWV.

   - Support for switch regulators on the PFUZE100, this required two
     goes due to backwards compatibility issues with old DTs that were
     discovered.

   - Support for Freescale PFUZE3001 and SocioNext UniPhier.

   - The aforementioned Qualcomm RPMH driver together with the driver
     changes required to support it"

* tag 'regulator-v4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator: (52 commits)
  regulator: add QCOM RPMh regulator driver
  regulator: dt-bindings: add QCOM RPMh regulator bindings
  regulator: samsung: Add SPDX license identifiers
  regulator: maxim: Add SPDX license identifiers
  regulator: bd71837: adobt MFD changes to regulator driver
  regulator: tps65217: Fix NULL pointer dereference on probe
  regulator: Add support for CPCAP regulators on Motorola Xoom devices.
  regulator: Add sw2_sw4 voltage table to cpcap regulator.
  regulator: bd9571mwv: Make symbol 'dev_attr_backup_mode' static
  regulator: pfuze100: add support to en-/disable switch regulators
  regulator: pfuze100: add optional disable switch-regulators binding
  soc: qcom: rmtfs-mem: fix memleak in probe error paths
  soc: qcom: llc-slice: Add missing MODULE_LICENSE()
  drivers: qcom: rpmh: fix unwanted error check for get_tcs_of_type()
  drivers: qcom: rpmh-rsc: fix the loop index check in get_req_from_tcs
  firmware: qcom: scm: add a dummy qcom_scm_assign_mem()
  drivers: qcom: rpmh-rsc: Check cmd_db_ready() to help children
  drivers: qcom: rpmh-rsc: allow active requests from wake TCS
  drivers: qcom: rpmh: add support for batch RPMH request
  drivers: qcom: rpmh: allow requests to be sent asynchronously
  ...
This commit is contained in:
Linus Torvalds 2018-08-14 12:04:49 -07:00
commit 3860cae64c
50 changed files with 4186 additions and 271 deletions

View File

@ -0,0 +1,27 @@
What: /sys/bus/i2c/devices/.../bd9571mwv-regulator.*.auto/backup_mode
Date: Jul 2018
KernelVersion: 4.19
Contact: Geert Uytterhoeven <geert+renesas@glider.be>
Description: Read/write the current state of DDR Backup Mode, which controls
if DDR power rails will be kept powered during system suspend.
("on"/"1" = enabled, "off"/"0" = disabled).
Two types of power switches (or control signals) can be used:
A. With a momentary power switch (or pulse signal), DDR
Backup Mode is enabled by default when available, as the
PMIC will be configured only during system suspend.
B. With a toggle power switch (or level signal), the
following steps must be followed exactly:
1. Configure PMIC for backup mode, to change the role of
the accessory power switch from a power switch to a
wake-up switch,
2. Switch accessory power switch off, to prepare for
system suspend, which is a manual step not controlled
by software,
3. Suspend system,
4. Switch accessory power switch on, to resume the
system.
DDR Backup Mode must be explicitly enabled by the user,
to invoke step 1.
See also Documentation/devicetree/bindings/mfd/bd9571mwv.txt.
Users: User space applications for embedded boards equipped with a
BD9571MWV PMIC.

View File

@ -0,0 +1,26 @@
== Introduction==
LLCC (Last Level Cache Controller) provides last level of cache memory in SOC,
that can be shared by multiple clients. Clients here are different cores in the
SOC, the idea is to minimize the local caches at the clients and migrate to
common pool of memory. Cache memory is divided into partitions called slices
which are assigned to clients. Clients can query the slice details, activate
and deactivate them.
Properties:
- compatible:
Usage: required
Value type: <string>
Definition: must be "qcom,sdm845-llcc"
- reg:
Usage: required
Value Type: <prop-encoded-array>
Definition: Start address and the the size of the register region.
Example:
cache-controller@1100000 {
compatible = "qcom,sdm845-llcc";
reg = <0x1100000 0x250000>;
};

View File

@ -5,6 +5,7 @@ Requires node properties:
- "compatible" value one of:
"motorola,cpcap-regulator"
"motorola,mapphone-cpcap-regulator"
"motorola,xoom-cpcap-regulator"
Required regulator properties:
- "regulator-name"

View File

@ -1,9 +1,18 @@
PFUZE100 family of regulators
Required properties:
- compatible: "fsl,pfuze100", "fsl,pfuze200", "fsl,pfuze3000"
- compatible: "fsl,pfuze100", "fsl,pfuze200", "fsl,pfuze3000", "fsl,pfuze3001"
- reg: I2C slave address
Optional properties:
- fsl,pfuze-support-disable-sw: Boolean, if present disable all unused switch
regulators to save power consumption. Attention, ensure that all important
regulators (e.g. DDR ref, DDR supply) has set the "regulator-always-on"
property. If not present, the switched regualtors are always on and can't be
disabled. This binding is a workaround to keep backward compatibility with
old dtb's which rely on the fact that the switched regulators are always on
and don't mark them explicit as "regulator-always-on".
Required child node:
- regulators: This is the list of child nodes that specify the regulator
initialization data for defined regulators. Please refer to below doc
@ -16,6 +25,8 @@ Required child node:
sw1ab,sw2,sw3a,sw3b,swbst,vsnvs,vrefddr,vgen1~vgen6,coin
--PFUZE3000
sw1a,sw1b,sw2,sw3,swbst,vsnvs,vrefddr,vldo1,vldo2,vccsd,v33,vldo3,vldo4
--PFUZE3001
sw1,sw2,sw3,vsnvs,vldo1,vldo2,vccsd,v33,vldo3,vldo4
Each regulator is defined using the standard binding for regulators.
@ -303,3 +314,76 @@ Example 3: PFUZE3000
};
};
};
Example 4: PFUZE 3001
pfuze3001: pmic@8 {
compatible = "fsl,pfuze3001";
reg = <0x08>;
regulators {
sw1_reg: sw1 {
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
sw2_reg: sw2 {
regulator-min-microvolt = <1500000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
sw3_reg: sw3 {
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1650000>;
regulator-boot-on;
regulator-always-on;
};
snvs_reg: vsnvs {
regulator-min-microvolt = <1000000>;
regulator-max-microvolt = <3000000>;
regulator-boot-on;
regulator-always-on;
};
vgen1_reg: vldo1 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vgen2_reg: vldo2 {
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1550000>;
regulator-always-on;
};
vgen3_reg: vccsd {
regulator-min-microvolt = <2850000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vgen4_reg: v33 {
regulator-min-microvolt = <2850000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vgen5_reg: vldo3 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
vgen6_reg: vldo4 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
};
};

View File

@ -0,0 +1,160 @@
Qualcomm Technologies, Inc. RPMh Regulators
rpmh-regulator devices support PMIC regulator management via the Voltage
Regulator Manager (VRM) and Oscillator Buffer (XOB) RPMh accelerators. The APPS
processor communicates with these hardware blocks via a Resource State
Coordinator (RSC) using command packets. The VRM allows changing three
parameters for a given regulator: enable state, output voltage, and operating
mode. The XOB allows changing only a single parameter for a given regulator:
its enable state. Despite its name, the XOB is capable of controlling the
enable state of any PMIC peripheral. It is used for clock buffers, low-voltage
switches, and LDO/SMPS regulators which have a fixed voltage and mode.
=======================
Required Node Structure
=======================
RPMh regulators must be described in two levels of device nodes. The first
level describes the PMIC containing the regulators and must reside within an
RPMh device node. The second level describes each regulator within the PMIC
which is to be used on the board. Each of these regulators maps to a single
RPMh resource.
The names used for regulator nodes must match those supported by a given PMIC.
Supported regulator node names:
PM8998: smps1 - smps13, ldo1 - ldo28, lvs1 - lvs2
PMI8998: bob
PM8005: smps1 - smps4
========================
First Level Nodes - PMIC
========================
- compatible
Usage: required
Value type: <string>
Definition: Must be one of: "qcom,pm8998-rpmh-regulators",
"qcom,pmi8998-rpmh-regulators" or
"qcom,pm8005-rpmh-regulators".
- qcom,pmic-id
Usage: required
Value type: <string>
Definition: RPMh resource name suffix used for the regulators found on
this PMIC. Typical values: "a", "b", "c", "d", "e", "f".
- vdd-s1-supply
- vdd-s2-supply
- vdd-s3-supply
- vdd-s4-supply
Usage: optional (PM8998 and PM8005 only)
Value type: <phandle>
Definition: phandle of the parent supply regulator of one or more of the
regulators for this PMIC.
- vdd-s5-supply
- vdd-s6-supply
- vdd-s7-supply
- vdd-s8-supply
- vdd-s9-supply
- vdd-s10-supply
- vdd-s11-supply
- vdd-s12-supply
- vdd-s13-supply
- vdd-l1-l27-supply
- vdd-l2-l8-l17-supply
- vdd-l3-l11-supply
- vdd-l4-l5-supply
- vdd-l6-supply
- vdd-l7-l12-l14-l15-supply
- vdd-l9-supply
- vdd-l10-l23-l25-supply
- vdd-l13-l19-l21-supply
- vdd-l16-l28-supply
- vdd-l18-l22-supply
- vdd-l20-l24-supply
- vdd-l26-supply
- vin-lvs-1-2-supply
Usage: optional (PM8998 only)
Value type: <phandle>
Definition: phandle of the parent supply regulator of one or more of the
regulators for this PMIC.
- vdd-bob-supply
Usage: optional (PMI8998 only)
Value type: <phandle>
Definition: BOB regulator parent supply phandle
===============================
Second Level Nodes - Regulators
===============================
- qcom,always-wait-for-ack
Usage: optional
Value type: <empty>
Definition: Boolean flag which indicates that the application processor
must wait for an ACK or a NACK from RPMh for every request
sent for this regulator including those which are for a
strictly lower power state.
Other properties defined in Documentation/devicetree/bindings/regulator.txt
may also be used. regulator-initial-mode and regulator-allowed-modes may be
specified for VRM regulators using mode values from
include/dt-bindings/regulator/qcom,rpmh-regulator.h. regulator-allow-bypass
may be specified for BOB type regulators managed via VRM.
regulator-allow-set-load may be specified for LDO type regulators managed via
VRM.
========
Examples
========
#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
&apps_rsc {
pm8998-rpmh-regulators {
compatible = "qcom,pm8998-rpmh-regulators";
qcom,pmic-id = "a";
vdd-l7-l12-l14-l15-supply = <&pm8998_s5>;
smps2 {
regulator-min-microvolt = <1100000>;
regulator-max-microvolt = <1100000>;
};
pm8998_s5: smps5 {
regulator-min-microvolt = <1904000>;
regulator-max-microvolt = <2040000>;
};
ldo7 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
regulator-initial-mode = <RPMH_REGULATOR_MODE_HPM>;
regulator-allowed-modes =
<RPMH_REGULATOR_MODE_LPM
RPMH_REGULATOR_MODE_HPM>;
regulator-allow-set-load;
};
lvs1 {
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <1800000>;
};
};
pmi8998-rpmh-regulators {
compatible = "qcom,pmi8998-rpmh-regulators";
qcom,pmic-id = "b";
bob {
regulator-min-microvolt = <3312000>;
regulator-max-microvolt = <3600000>;
regulator-allowed-modes =
<RPMH_REGULATOR_MODE_AUTO
RPMH_REGULATOR_MODE_HPM>;
regulator-initial-mode = <RPMH_REGULATOR_MODE_AUTO>;
};
};
};

View File

@ -1,13 +1,5 @@
ROHM BD71837 Power Management Integrated Circuit (PMIC) regulator bindings
BD71837MWV is a programmable Power Management
IC (PMIC) for powering single-core, dual-core, and
quad-core SoCs such as NXP-i.MX 8M. It is optimized
for low BOM cost and compact solution footprint. It
integrates 8 Buck regulators and 7 LDOs to provide all
the power rails required by the SoC and the commonly
used peripherals.
Required properties:
- regulator-name: should be "buck1", ..., "buck8" and "ldo1", ..., "ldo7"

View File

@ -0,0 +1,57 @@
Socionext UniPhier Regulator Controller
This describes the devicetree bindings for regulator controller implemented
on Socionext UniPhier SoCs.
USB3 Controller
---------------
This regulator controls VBUS and belongs to USB3 glue layer. Before using
the regulator, it is necessary to control the clocks and resets to enable
this layer. These clocks and resets should be described in each property.
Required properties:
- compatible: Should be
"socionext,uniphier-pro4-usb3-regulator" - for Pro4 SoC
"socionext,uniphier-pxs2-usb3-regulator" - for PXs2 SoC
"socionext,uniphier-ld20-usb3-regulator" - for LD20 SoC
"socionext,uniphier-pxs3-usb3-regulator" - for PXs3 SoC
- reg: Specifies offset and length of the register set for the device.
- clocks: A list of phandles to the clock gate for USB3 glue layer.
According to the clock-names, appropriate clocks are required.
- clock-names: Should contain
"gio", "link" - for Pro4 SoC
"link" - for others
- resets: A list of phandles to the reset control for USB3 glue layer.
According to the reset-names, appropriate resets are required.
- reset-names: Should contain
"gio", "link" - for Pro4 SoC
"link" - for others
See Documentation/devicetree/bindings/regulator/regulator.txt
for more details about the regulator properties.
Example:
usb-glue@65b00000 {
compatible = "socionext,uniphier-ld20-dwc3-glue",
"simple-mfd";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0 0x65b00000 0x400>;
usb_vbus0: regulators@100 {
compatible = "socionext,uniphier-ld20-usb3-regulator";
reg = <0x100 0x10>;
clock-names = "link";
clocks = <&sys_clk 14>;
reset-names = "link";
resets = <&sys_rst 14>;
};
phy {
...
phy-supply = <&usb_vbus0>;
};
...
};

View File

@ -0,0 +1,137 @@
RPMH RSC:
------------
Resource Power Manager Hardened (RPMH) is the mechanism for communicating with
the hardened resource accelerators on Qualcomm SoCs. Requests to the resources
can be written to the Trigger Command Set (TCS) registers and using a (addr,
val) pair and triggered. Messages in the TCS are then sent in sequence over an
internal bus.
The hardware block (Direct Resource Voter or DRV) is a part of the h/w entity
(Resource State Coordinator a.k.a RSC) that can handle multiple sleep and
active/wake resource requests. Multiple such DRVs can exist in a SoC and can
be written to from Linux. The structure of each DRV follows the same template
with a few variations that are captured by the properties here.
A TCS may be triggered from Linux or triggered by the F/W after all the CPUs
have powered off to facilitate idle power saving. TCS could be classified as -
ACTIVE /* Triggered by Linux */
SLEEP /* Triggered by F/W */
WAKE /* Triggered by F/W */
CONTROL /* Triggered by F/W */
The order in which they are described in the DT, should match the hardware
configuration.
Requests can be made for the state of a resource, when the subsystem is active
or idle. When all subsystems like Modem, GPU, CPU are idle, the resource state
will be an aggregate of the sleep votes from each of those subsystems. Clients
may request a sleep value for their shared resources in addition to the active
mode requests.
Properties:
- compatible:
Usage: required
Value type: <string>
Definition: Should be "qcom,rpmh-rsc".
- reg:
Usage: required
Value type: <prop-encoded-array>
Definition: The first register specifies the base address of the
DRV(s). The number of DRVs in the dependent on the RSC.
The tcs-offset specifies the start address of the
TCS in the DRVs.
- reg-names:
Usage: required
Value type: <string>
Definition: Maps the register specified in the reg property. Must be
"drv-0", "drv-1", "drv-2" etc and "tcs-offset". The
- interrupts:
Usage: required
Value type: <prop-encoded-interrupt>
Definition: The interrupt that trips when a message complete/response
is received for this DRV from the accelerators.
- qcom,drv-id:
Usage: required
Value type: <u32>
Definition: The id of the DRV in the RSC block that will be used by
this controller.
- qcom,tcs-config:
Usage: required
Value type: <prop-encoded-array>
Definition: The tuple defining the configuration of TCS.
Must have 2 cells which describe each TCS type.
<type number_of_tcs>.
The order of the TCS must match the hardware
configuration.
- Cell #1 (TCS Type): TCS types to be specified -
ACTIVE_TCS
SLEEP_TCS
WAKE_TCS
CONTROL_TCS
- Cell #2 (Number of TCS): <u32>
- label:
Usage: optional
Value type: <string>
Definition: Name for the RSC. The name would be used in trace logs.
Drivers that want to use the RSC to communicate with RPMH must specify their
bindings as child nodes of the RSC controllers they wish to communicate with.
Example 1:
For a TCS whose RSC base address is is 0x179C0000 and is at a DRV id of 2, the
register offsets for DRV2 start at 0D00, the register calculations are like
this -
DRV0: 0x179C0000
DRV2: 0x179C0000 + 0x10000 = 0x179D0000
DRV2: 0x179C0000 + 0x10000 * 2 = 0x179E0000
TCS-OFFSET: 0xD00
apps_rsc: rsc@179c0000 {
label = "apps_rsc";
compatible = "qcom,rpmh-rsc";
reg = <0x179c0000 0x10000>,
<0x179d0000 0x10000>,
<0x179e0000 0x10000>;
reg-names = "drv-0", "drv-1", "drv-2";
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
qcom,tcs-offset = <0xd00>;
qcom,drv-id = <2>;
qcom,tcs-config = <ACTIVE_TCS 2>,
<SLEEP_TCS 3>,
<WAKE_TCS 3>,
<CONTROL_TCS 1>;
};
Example 2:
For a TCS whose RSC base address is 0xAF20000 and is at DRV id of 0, the
register offsets for DRV0 start at 01C00, the register calculations are like
this -
DRV0: 0xAF20000
TCS-OFFSET: 0x1C00
disp_rsc: rsc@af20000 {
label = "disp_rsc";
compatible = "qcom,rpmh-rsc";
reg = <0xaf20000 0x10000>;
reg-names = "drv-0";
interrupts = <GIC_SPI 129 IRQ_TYPE_LEVEL_HIGH>;
qcom,tcs-offset = <0x1c00>;
qcom,drv-id = <0>;
qcom,tcs-config = <ACTIVE_TCS 0>,
<SLEEP_TCS 1>,
<WAKE_TCS 1>,
<CONTROL_TCS 0>;
};

View File

@ -372,6 +372,36 @@ void device_link_del(struct device_link *link)
}
EXPORT_SYMBOL_GPL(device_link_del);
/**
* device_link_remove - remove a link between two devices.
* @consumer: Consumer end of the link.
* @supplier: Supplier end of the link.
*
* The caller must ensure proper synchronization of this function with runtime
* PM.
*/
void device_link_remove(void *consumer, struct device *supplier)
{
struct device_link *link;
if (WARN_ON(consumer == supplier))
return;
device_links_write_lock();
device_pm_lock();
list_for_each_entry(link, &supplier->links.consumers, s_node) {
if (link->consumer == consumer) {
kref_put(&link->kref, __device_link_del);
break;
}
}
device_pm_unlock();
device_links_write_unlock();
}
EXPORT_SYMBOL_GPL(device_link_remove);
static void device_links_missing_supplier(struct device *dev)
{
struct device_link *link;

View File

@ -180,9 +180,9 @@ config REGULATOR_BCM590XX
BCM590xx PMUs. This will enable support for the software
controllable LDO/Switching regulators.
config REGULATOR_BD71837
config REGULATOR_BD718XX
tristate "ROHM BD71837 Power Regulator"
depends on MFD_BD71837
depends on MFD_ROHM_BD718XX
help
This driver supports voltage regulators on ROHM BD71837 PMIC.
This will enable support for the software controllable buck
@ -633,12 +633,12 @@ config REGULATOR_PCF50633
on PCF50633
config REGULATOR_PFUZE100
tristate "Freescale PFUZE100/200/3000 regulator driver"
tristate "Freescale PFUZE100/200/3000/3001 regulator driver"
depends on I2C
select REGMAP_I2C
help
Say y here to support the regulators found on the Freescale
PFUZE100/200/3000 PMIC.
PFUZE100/200/3000/3001 PMIC.
config REGULATOR_PV88060
tristate "Powerventure Semiconductor PV88060 regulator"
@ -682,6 +682,15 @@ config REGULATOR_QCOM_RPM
Qualcomm RPM as a module. The module will be named
"qcom_rpm-regulator".
config REGULATOR_QCOM_RPMH
tristate "Qualcomm Technologies, Inc. RPMh regulator driver"
depends on QCOM_RPMH || COMPILE_TEST
help
This driver supports control of PMIC regulators via the RPMh hardware
block found on Qualcomm Technologies Inc. SoCs. RPMh regulator
control allows for voting on regulator state between multiple
processors within the SoC.
config REGULATOR_QCOM_SMD_RPM
tristate "Qualcomm SMD based RPM regulator driver"
depends on QCOM_SMD_RPM
@ -950,6 +959,14 @@ config REGULATOR_TWL4030
This driver supports the voltage regulators provided by
this family of companion chips.
config REGULATOR_UNIPHIER
tristate "UniPhier regulator driver"
depends on ARCH_UNIPHIER || COMPILE_TEST
depends on OF && MFD_SYSCON
default ARCH_UNIPHIER
help
Support for regulators implemented on Socionext UniPhier SoCs.
config REGULATOR_VCTRL
tristate "Voltage controlled regulators"
depends on OF

View File

@ -27,7 +27,7 @@ obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o
obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o
obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o
obj-$(CONFIG_REGULATOR_BCM590XX) += bcm590xx-regulator.o
obj-$(CONFIG_REGULATOR_BD71837) += bd71837-regulator.o
obj-$(CONFIG_REGULATOR_BD718XX) += bd71837-regulator.o
obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv-regulator.o
obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o
@ -78,6 +78,7 @@ obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o
obj-$(CONFIG_REGULATOR_MT6380) += mt6380-regulator.o
obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
@ -118,6 +119,7 @@ obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o
obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o
obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o
obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o
obj-$(CONFIG_REGULATOR_UNIPHIER) += uniphier-regulator.o
obj-$(CONFIG_REGULATOR_VCTRL) += vctrl-regulator.o
obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress-regulator.o
obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o

View File

@ -36,6 +36,8 @@ struct arizona_ldo1 {
struct regulator_consumer_supply supply;
struct regulator_init_data init_data;
struct gpio_desc *ena_gpiod;
};
static int arizona_ldo1_hc_list_voltage(struct regulator_dev *rdev,
@ -253,12 +255,17 @@ static int arizona_ldo1_common_init(struct platform_device *pdev,
}
}
/* We assume that high output = regulator off */
config.ena_gpiod = devm_gpiod_get_optional(&pdev->dev, "wlf,ldoena",
GPIOD_OUT_HIGH);
/* We assume that high output = regulator off
* Don't use devm, since we need to get against the parent device
* so clean up would happen at the wrong time
*/
config.ena_gpiod = gpiod_get_optional(parent_dev, "wlf,ldoena",
GPIOD_OUT_LOW);
if (IS_ERR(config.ena_gpiod))
return PTR_ERR(config.ena_gpiod);
ldo1->ena_gpiod = config.ena_gpiod;
if (pdata->init_data)
config.init_data = pdata->init_data;
else
@ -276,6 +283,9 @@ static int arizona_ldo1_common_init(struct platform_device *pdev,
of_node_put(config.of_node);
if (IS_ERR(ldo1->regulator)) {
if (config.ena_gpiod)
gpiod_put(config.ena_gpiod);
ret = PTR_ERR(ldo1->regulator);
dev_err(&pdev->dev, "Failed to register LDO1 supply: %d\n",
ret);
@ -334,8 +344,19 @@ static int arizona_ldo1_probe(struct platform_device *pdev)
return ret;
}
static int arizona_ldo1_remove(struct platform_device *pdev)
{
struct arizona_ldo1 *ldo1 = platform_get_drvdata(pdev);
if (ldo1->ena_gpiod)
gpiod_put(ldo1->ena_gpiod);
return 0;
}
static struct platform_driver arizona_ldo1_driver = {
.probe = arizona_ldo1_probe,
.remove = arizona_ldo1_remove,
.driver = {
.name = "arizona-ldo1",
},

View File

@ -2,19 +2,18 @@
// Copyright (C) 2018 ROHM Semiconductors
// bd71837-regulator.c ROHM BD71837MWV regulator driver
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/rohm-bd718x7.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/mfd/bd71837.h>
#include <linux/regulator/of_regulator.h>
#include <linux/slab.h>
struct bd71837_pmic {
struct regulator_desc descs[BD71837_REGULATOR_CNT];
@ -39,7 +38,7 @@ static int bd71837_buck1234_set_ramp_delay(struct regulator_dev *rdev,
int id = rdev->desc->id;
unsigned int ramp_value = BUCK_RAMPRATE_10P00MV;
dev_dbg(&(pmic->pdev->dev), "Buck[%d] Set Ramp = %d\n", id + 1,
dev_dbg(&pmic->pdev->dev, "Buck[%d] Set Ramp = %d\n", id + 1,
ramp_delay);
switch (ramp_delay) {
case 1 ... 1250:
@ -73,14 +72,10 @@ static int bd71837_buck1234_set_ramp_delay(struct regulator_dev *rdev,
static int bd71837_set_voltage_sel_restricted(struct regulator_dev *rdev,
unsigned int sel)
{
int ret;
if (regulator_is_enabled_regmap(rdev))
return -EBUSY;
ret = regulator_is_enabled_regmap(rdev);
if (!ret)
ret = regulator_set_voltage_sel_regmap(rdev, sel);
else if (ret == 1)
ret = -EBUSY;
return ret;
return regulator_set_voltage_sel_regmap(rdev, sel);
}
static struct regulator_ops bd71837_ldo_regulator_ops = {
@ -195,7 +190,7 @@ static const struct regulator_linear_range bd71837_ldo1_voltage_ranges[] = {
* LDO2
* 0.8 or 0.9V
*/
const unsigned int ldo_2_volts[] = {
static const unsigned int ldo_2_volts[] = {
900000, 800000
};
@ -495,7 +490,6 @@ struct reg_init {
static int bd71837_probe(struct platform_device *pdev)
{
struct bd71837_pmic *pmic;
struct bd71837_board *pdata;
struct regulator_config config = { 0 };
struct reg_init pmic_regulator_inits[] = {
{
@ -548,8 +542,7 @@ static int bd71837_probe(struct platform_device *pdev)
int i, err;
pmic = devm_kzalloc(&pdev->dev, sizeof(struct bd71837_pmic),
GFP_KERNEL);
pmic = devm_kzalloc(&pdev->dev, sizeof(*pmic), GFP_KERNEL);
if (!pmic)
return -ENOMEM;
@ -564,7 +557,6 @@ static int bd71837_probe(struct platform_device *pdev)
goto err;
}
platform_set_drvdata(pdev, pmic);
pdata = dev_get_platdata(pmic->mfd->dev);
/* Register LOCK release */
err = regmap_update_bits(pmic->mfd->regmap, BD71837_REG_REGLOCK,
@ -573,8 +565,8 @@ static int bd71837_probe(struct platform_device *pdev)
dev_err(&pmic->pdev->dev, "Failed to unlock PMIC (%d)\n", err);
goto err;
} else {
dev_dbg(&pmic->pdev->dev, "%s: Unlocked lock register 0x%x\n",
__func__, BD71837_REG_REGLOCK);
dev_dbg(&pmic->pdev->dev, "Unlocked lock register 0x%x\n",
BD71837_REG_REGLOCK);
}
for (i = 0; i < ARRAY_SIZE(pmic_regulator_inits); i++) {
@ -584,9 +576,6 @@ static int bd71837_probe(struct platform_device *pdev)
desc = &pmic->descs[i];
if (pdata)
config.init_data = pdata->init_data[i];
config.dev = pdev->dev.parent;
config.driver_data = pmic;
config.regmap = pmic->mfd->regmap;
@ -619,8 +608,6 @@ static int bd71837_probe(struct platform_device *pdev)
pmic->rdev[i] = rdev;
}
return 0;
err:
return err;
}
@ -628,7 +615,6 @@ err:
static struct platform_driver bd71837_regulator = {
.driver = {
.name = "bd71837-pmic",
.owner = THIS_MODULE,
},
.probe = bd71837_probe,
};

View File

@ -30,6 +30,7 @@ struct bd9571mwv_reg {
/* DDR Backup Power */
u8 bkup_mode_cnt_keepon; /* from "rohm,ddr-backup-power" */
u8 bkup_mode_cnt_saved;
bool bkup_mode_enabled;
/* Power switch type */
bool rstbmode_level;
@ -171,13 +172,60 @@ static int bd9571mwv_bkup_mode_write(struct bd9571mwv *bd, unsigned int mode)
return 0;
}
static ssize_t backup_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", bdreg->bkup_mode_enabled ? "on" : "off");
}
static ssize_t backup_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
unsigned int mode;
int ret;
if (!count)
return 0;
ret = kstrtobool(buf, &bdreg->bkup_mode_enabled);
if (ret)
return ret;
if (!bdreg->rstbmode_level)
return count;
/*
* Configure DDR Backup Mode, to change the role of the accessory power
* switch from a power switch to a wake-up switch, or vice versa
*/
ret = bd9571mwv_bkup_mode_read(bdreg->bd, &mode);
if (ret)
return ret;
mode &= ~BD9571MWV_BKUP_MODE_CNT_KEEPON_MASK;
if (bdreg->bkup_mode_enabled)
mode |= bdreg->bkup_mode_cnt_keepon;
ret = bd9571mwv_bkup_mode_write(bdreg->bd, mode);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_RW(backup_mode);
static int bd9571mwv_suspend(struct device *dev)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
unsigned int mode;
int ret;
if (!device_may_wakeup(dev))
if (!bdreg->bkup_mode_enabled)
return 0;
/* Save DDR Backup Mode */
@ -204,7 +252,7 @@ static int bd9571mwv_resume(struct device *dev)
{
struct bd9571mwv_reg *bdreg = dev_get_drvdata(dev);
if (!device_may_wakeup(dev))
if (!bdreg->bkup_mode_enabled)
return 0;
/* Restore DDR Backup Mode */
@ -215,9 +263,15 @@ static const struct dev_pm_ops bd9571mwv_pm = {
SET_SYSTEM_SLEEP_PM_OPS(bd9571mwv_suspend, bd9571mwv_resume)
};
static int bd9571mwv_regulator_remove(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_backup_mode);
return 0;
}
#define DEV_PM_OPS &bd9571mwv_pm
#else
#define DEV_PM_OPS NULL
#define bd9571mwv_regulator_remove NULL
#endif /* CONFIG_PM_SLEEP */
static int bd9571mwv_regulator_probe(struct platform_device *pdev)
@ -270,14 +324,21 @@ static int bd9571mwv_regulator_probe(struct platform_device *pdev)
return -EINVAL;
}
#ifdef CONFIG_PM_SLEEP
if (bdreg->bkup_mode_cnt_keepon) {
device_set_wakeup_capable(&pdev->dev, true);
int ret;
/*
* Wakeup is enabled by default in pulse mode, but needs
* Backup mode is enabled by default in pulse mode, but needs
* explicit user setup in level mode.
*/
device_set_wakeup_enable(&pdev->dev, bdreg->rstbmode_pulse);
bdreg->bkup_mode_enabled = bdreg->rstbmode_pulse;
ret = device_create_file(&pdev->dev, &dev_attr_backup_mode);
if (ret)
return ret;
}
#endif /* CONFIG_PM_SLEEP */
return 0;
}
@ -294,6 +355,7 @@ static struct platform_driver bd9571mwv_regulator_driver = {
.pm = DEV_PM_OPS,
},
.probe = bd9571mwv_regulator_probe,
.remove = bd9571mwv_regulator_remove,
.id_table = bd9571mwv_regulator_id_table,
};
module_platform_driver(bd9571mwv_regulator_driver);

View File

@ -1740,6 +1740,8 @@ struct regulator *_regulator_get(struct device *dev, const char *id,
rdev->use_count = 0;
}
device_link_add(dev, &rdev->dev, DL_FLAG_STATELESS);
return regulator;
}
@ -1829,9 +1831,21 @@ static void _regulator_put(struct regulator *regulator)
debugfs_remove_recursive(regulator->debugfs);
/* remove any sysfs entries */
if (regulator->dev)
if (regulator->dev) {
int count = 0;
struct regulator *r;
list_for_each_entry(r, &rdev->consumer_list, list)
if (r->dev == regulator->dev)
count++;
if (count == 1)
device_link_remove(regulator->dev, &rdev->dev);
/* remove any sysfs entries */
sysfs_remove_link(&rdev->dev.kobj, regulator->supply_name);
}
regulator_lock(rdev);
list_del(&regulator->list);
@ -4441,7 +4455,7 @@ void regulator_unregister(struct regulator_dev *rdev)
EXPORT_SYMBOL_GPL(regulator_unregister);
#ifdef CONFIG_SUSPEND
static int _regulator_suspend_late(struct device *dev, void *data)
static int _regulator_suspend(struct device *dev, void *data)
{
struct regulator_dev *rdev = dev_to_rdev(dev);
suspend_state_t *state = data;
@ -4455,20 +4469,20 @@ static int _regulator_suspend_late(struct device *dev, void *data)
}
/**
* regulator_suspend_late - prepare regulators for system wide suspend
* regulator_suspend - prepare regulators for system wide suspend
* @state: system suspend state
*
* Configure each regulator with it's suspend operating parameters for state.
*/
static int regulator_suspend_late(struct device *dev)
static int regulator_suspend(struct device *dev)
{
suspend_state_t state = pm_suspend_target_state;
return class_for_each_device(&regulator_class, NULL, &state,
_regulator_suspend_late);
_regulator_suspend);
}
static int _regulator_resume_early(struct device *dev, void *data)
static int _regulator_resume(struct device *dev, void *data)
{
int ret = 0;
struct regulator_dev *rdev = dev_to_rdev(dev);
@ -4481,35 +4495,35 @@ static int _regulator_resume_early(struct device *dev, void *data)
regulator_lock(rdev);
if (rdev->desc->ops->resume_early &&
if (rdev->desc->ops->resume &&
(rstate->enabled == ENABLE_IN_SUSPEND ||
rstate->enabled == DISABLE_IN_SUSPEND))
ret = rdev->desc->ops->resume_early(rdev);
ret = rdev->desc->ops->resume(rdev);
regulator_unlock(rdev);
return ret;
}
static int regulator_resume_early(struct device *dev)
static int regulator_resume(struct device *dev)
{
suspend_state_t state = pm_suspend_target_state;
return class_for_each_device(&regulator_class, NULL, &state,
_regulator_resume_early);
_regulator_resume);
}
#else /* !CONFIG_SUSPEND */
#define regulator_suspend_late NULL
#define regulator_resume_early NULL
#define regulator_suspend NULL
#define regulator_resume NULL
#endif /* !CONFIG_SUSPEND */
#ifdef CONFIG_PM
static const struct dev_pm_ops __maybe_unused regulator_pm_ops = {
.suspend_late = regulator_suspend_late,
.resume_early = regulator_resume_early,
.suspend = regulator_suspend,
.resume = regulator_resume,
};
#endif

View File

@ -271,6 +271,29 @@ static struct regulator_ops cpcap_regulator_ops = {
};
static const unsigned int unknown_val_tbl[] = { 0, };
static const unsigned int sw2_sw4_val_tbl[] = { 612500, 625000, 637500,
650000, 662500, 675000,
687500, 700000, 712500,
725000, 737500, 750000,
762500, 775000, 787500,
800000, 812500, 825000,
837500, 850000, 862500,
875000, 887500, 900000,
912500, 925000, 937500,
950000, 962500, 975000,
987500, 1000000, 1012500,
1025000, 1037500, 1050000,
1062500, 1075000, 1087500,
1100000, 1112500, 1125000,
1137500, 1150000, 1162500,
1175000, 1187500, 1200000,
1212500, 1225000, 1237500,
1250000, 1262500, 1275000,
1287500, 1300000, 1312500,
1325000, 1337500, 1350000,
1362500, 1375000, 1387500,
1400000, 1412500, 1425000,
1437500, 1450000, 1462500, };
static const unsigned int sw5_val_tbl[] = { 0, 5050000, };
static const unsigned int vcam_val_tbl[] = { 2600000, 2700000, 2800000,
2900000, };
@ -389,6 +412,82 @@ static struct cpcap_regulator omap4_regulators[] = {
{ /* sentinel */ },
};
static struct cpcap_regulator xoom_regulators[] = {
CPCAP_REG(SW1, CPCAP_REG_S1C1, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW1_SEL, unknown_val_tbl,
0, 0, 0, 0, 0, 0),
CPCAP_REG(SW2, CPCAP_REG_S2C1, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW2_SEL, sw2_sw4_val_tbl,
0xf00, 0x7f, 0, 0x800, 0, 120),
CPCAP_REG(SW3, CPCAP_REG_S3C, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW3_SEL, unknown_val_tbl,
0, 0, 0, 0, 0, 0),
CPCAP_REG(SW4, CPCAP_REG_S4C1, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW4_SEL, sw2_sw4_val_tbl,
0xf00, 0x7f, 0, 0x900, 0, 100),
CPCAP_REG(SW5, CPCAP_REG_S5C, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW5_SEL, sw5_val_tbl,
0x2a, 0, 0, 0x22, 0, 0),
CPCAP_REG(SW6, CPCAP_REG_S6C, CPCAP_REG_ASSIGN2,
CPCAP_BIT_SW6_SEL, unknown_val_tbl,
0, 0, 0, 0, 0, 0),
CPCAP_REG(VCAM, CPCAP_REG_VCAMC, CPCAP_REG_ASSIGN2,
CPCAP_BIT_VCAM_SEL, vcam_val_tbl,
0x87, 0x30, 4, 0x7, 0, 420),
CPCAP_REG(VCSI, CPCAP_REG_VCSIC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VCSI_SEL, vcsi_val_tbl,
0x47, 0x10, 4, 0x7, 0, 350),
CPCAP_REG(VDAC, CPCAP_REG_VDACC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VDAC_SEL, vdac_val_tbl,
0x87, 0x30, 4, 0x3, 0, 420),
CPCAP_REG(VDIG, CPCAP_REG_VDIGC, CPCAP_REG_ASSIGN2,
CPCAP_BIT_VDIG_SEL, vdig_val_tbl,
0x87, 0x30, 4, 0x5, 0, 420),
CPCAP_REG(VFUSE, CPCAP_REG_VFUSEC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VFUSE_SEL, vfuse_val_tbl,
0x80, 0xf, 0, 0x80, 0, 420),
CPCAP_REG(VHVIO, CPCAP_REG_VHVIOC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VHVIO_SEL, vhvio_val_tbl,
0x17, 0, 0, 0x2, 0, 0),
CPCAP_REG(VSDIO, CPCAP_REG_VSDIOC, CPCAP_REG_ASSIGN2,
CPCAP_BIT_VSDIO_SEL, vsdio_val_tbl,
0x87, 0x38, 3, 0x2, 0, 420),
CPCAP_REG(VPLL, CPCAP_REG_VPLLC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VPLL_SEL, vpll_val_tbl,
0x43, 0x18, 3, 0x1, 0, 420),
CPCAP_REG(VRF1, CPCAP_REG_VRF1C, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VRF1_SEL, vrf1_val_tbl,
0xac, 0x2, 1, 0xc, 0, 10),
CPCAP_REG(VRF2, CPCAP_REG_VRF2C, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VRF2_SEL, vrf2_val_tbl,
0x23, 0x8, 3, 0x3, 0, 10),
CPCAP_REG(VRFREF, CPCAP_REG_VRFREFC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VRFREF_SEL, vrfref_val_tbl,
0x23, 0x8, 3, 0x3, 0, 420),
CPCAP_REG(VWLAN1, CPCAP_REG_VWLAN1C, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VWLAN1_SEL, vwlan1_val_tbl,
0x47, 0x10, 4, 0x5, 0, 420),
CPCAP_REG(VWLAN2, CPCAP_REG_VWLAN2C, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VWLAN2_SEL, vwlan2_val_tbl,
0x20c, 0xc0, 6, 0x8, 0, 420),
CPCAP_REG(VSIM, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3,
0xffff, vsim_val_tbl,
0x23, 0x8, 3, 0x3, 0, 420),
CPCAP_REG(VSIMCARD, CPCAP_REG_VSIMC, CPCAP_REG_ASSIGN3,
0xffff, vsimcard_val_tbl,
0x1e80, 0x8, 3, 0x1e00, 0, 420),
CPCAP_REG(VVIB, CPCAP_REG_VVIBC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VVIB_SEL, vvib_val_tbl,
0x1, 0xc, 2, 0, 0x1, 500),
CPCAP_REG(VUSB, CPCAP_REG_VUSBC, CPCAP_REG_ASSIGN3,
CPCAP_BIT_VUSB_SEL, vusb_val_tbl,
0x11c, 0x40, 6, 0xc, 0, 0),
CPCAP_REG(VAUDIO, CPCAP_REG_VAUDIOC, CPCAP_REG_ASSIGN4,
CPCAP_BIT_VAUDIO_SEL, vaudio_val_tbl,
0x16, 0x1, 0, 0x4, 0, 0),
{ /* sentinel */ },
};
static const struct of_device_id cpcap_regulator_id_table[] = {
{
.compatible = "motorola,cpcap-regulator",
@ -397,6 +496,10 @@ static const struct of_device_id cpcap_regulator_id_table[] = {
.compatible = "motorola,mapphone-cpcap-regulator",
.data = omap4_regulators,
},
{
.compatible = "motorola,xoom-cpcap-regulator",
.data = xoom_regulators,
},
{},
};
MODULE_DEVICE_TABLE(of, cpcap_regulator_id_table);

View File

@ -1,19 +1,9 @@
/*
* max14577.c - Regulator driver for the Maxim 14577/77836
*
* Copyright (C) 2013,2014 Samsung Electronics
* Krzysztof Kozlowski <krzk@kernel.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max14577.c - Regulator driver for the Maxim 14577/77836
//
// Copyright (C) 2013,2014 Samsung Electronics
// Krzysztof Kozlowski <krzk@kernel.org>
#include <linux/module.h>
#include <linux/platform_device.h>

View File

@ -1,26 +1,12 @@
/*
* max77686.c - Regulator driver for the Maxim 77686
*
* Copyright (C) 2012 Samsung Electronics
* Chiwoong Byun <woong.byun@samsung.com>
* Jonghwa Lee <jonghwa3.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* This driver is based on max8997.c
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max77686.c - Regulator driver for the Maxim 77686
//
// Copyright (C) 2012 Samsung Electronics
// Chiwoong Byun <woong.byun@samsung.com>
// Jonghwa Lee <jonghwa3.lee@samsung.com>
//
// This driver is based on max8997.c
#include <linux/kernel.h>
#include <linux/bug.h>

View File

@ -1,26 +1,12 @@
/*
* max77693.c - Regulator driver for the Maxim 77693 and 77843
*
* Copyright (C) 2013-2015 Samsung Electronics
* Jonghwa Lee <jonghwa3.lee@samsung.com>
* Krzysztof Kozlowski <krzk@kernel.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* This driver is based on max77686.c
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max77693.c - Regulator driver for the Maxim 77693 and 77843
//
// Copyright (C) 2013-2015 Samsung Electronics
// Jonghwa Lee <jonghwa3.lee@samsung.com>
// Krzysztof Kozlowski <krzk@kernel.org>
//
// This driver is based on max77686.c
#include <linux/err.h>
#include <linux/slab.h>

View File

@ -1,25 +1,15 @@
/*
* max77802.c - Regulator driver for the Maxim 77802
*
* Copyright (C) 2013-2014 Google, Inc
* Simon Glass <sjg@chromium.org>
*
* Copyright (C) 2012 Samsung Electronics
* Chiwoong Byun <woong.byun@samsung.com>
* Jonghwa Lee <jonghwa3.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* This driver is based on max8997.c
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max77802.c - Regulator driver for the Maxim 77802
//
// Copyright (C) 2013-2014 Google, Inc
// Simon Glass <sjg@chromium.org>
//
// Copyright (C) 2012 Samsung Electronics
// Chiwoong Byun <woong.byun@samsung.com>
// Jonghwa Lee <jonghwa3.lee@samsung.com>
//
// This driver is based on max8997.c
#include <linux/kernel.h>
#include <linux/bug.h>

View File

@ -1,25 +1,11 @@
/*
* max8997.c - Regulator driver for the Maxim 8997/8966
*
* Copyright (C) 2011 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* This driver is based on max8998.c
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max8997.c - Regulator driver for the Maxim 8997/8966
//
// Copyright (C) 2011 Samsung Electronics
// MyungJoo Ham <myungjoo.ham@samsung.com>
//
// This driver is based on max8998.c
#include <linux/bug.h>
#include <linux/err.h>
@ -165,8 +151,7 @@ static int max8997_list_voltage(struct regulator_dev *rdev,
int rid = rdev_get_id(rdev);
int val;
if (rid >= ARRAY_SIZE(reg_voltage_map) ||
rid < 0)
if (rid < 0 || rid >= ARRAY_SIZE(reg_voltage_map))
return -EINVAL;
desc = reg_voltage_map[rid];

View File

@ -1,24 +1,10 @@
/*
* max8998.c - Voltage regulator driver for the Maxim 8998
*
* Copyright (C) 2009-2010 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
* Marek Szyprowski <m.szyprowski@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// SPDX-License-Identifier: GPL-2.0+
//
// max8998.c - Voltage regulator driver for the Maxim 8998
//
// Copyright (C) 2009-2010 Samsung Electronics
// Kyungmin Park <kyungmin.park@samsung.com>
// Marek Szyprowski <m.szyprowski@samsung.com>
#include <linux/module.h>
#include <linux/init.h>

View File

@ -17,6 +17,8 @@
#include <linux/slab.h>
#include <linux/regmap.h>
#define PFUZE_FLAG_DISABLE_SW BIT(1)
#define PFUZE_NUMREGS 128
#define PFUZE100_VOL_OFFSET 0
#define PFUZE100_STANDBY_OFFSET 1
@ -44,16 +46,18 @@
#define PFUZE100_VGEN5VOL 0x70
#define PFUZE100_VGEN6VOL 0x71
enum chips { PFUZE100, PFUZE200, PFUZE3000 = 3 };
enum chips { PFUZE100, PFUZE200, PFUZE3000 = 3, PFUZE3001 = 0x31, };
struct pfuze_regulator {
struct regulator_desc desc;
unsigned char stby_reg;
unsigned char stby_mask;
bool sw_reg;
};
struct pfuze_chip {
int chip_id;
int flags;
struct regmap *regmap;
struct device *dev;
struct pfuze_regulator regulator_descs[PFUZE100_MAX_REGULATOR];
@ -92,6 +96,7 @@ static const struct i2c_device_id pfuze_device_id[] = {
{.name = "pfuze100", .driver_data = PFUZE100},
{.name = "pfuze200", .driver_data = PFUZE200},
{.name = "pfuze3000", .driver_data = PFUZE3000},
{.name = "pfuze3001", .driver_data = PFUZE3001},
{ }
};
MODULE_DEVICE_TABLE(i2c, pfuze_device_id);
@ -100,6 +105,7 @@ static const struct of_device_id pfuze_dt_ids[] = {
{ .compatible = "fsl,pfuze100", .data = (void *)PFUZE100},
{ .compatible = "fsl,pfuze200", .data = (void *)PFUZE200},
{ .compatible = "fsl,pfuze3000", .data = (void *)PFUZE3000},
{ .compatible = "fsl,pfuze3001", .data = (void *)PFUZE3001},
{ }
};
MODULE_DEVICE_TABLE(of, pfuze_dt_ids);
@ -108,10 +114,28 @@ static int pfuze100_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay)
{
struct pfuze_chip *pfuze100 = rdev_get_drvdata(rdev);
int id = rdev_get_id(rdev);
bool reg_has_ramp_delay;
unsigned int ramp_bits;
int ret;
if (id < PFUZE100_SWBST) {
switch (pfuze100->chip_id) {
case PFUZE3001:
/* no dynamic voltage scaling for PF3001 */
reg_has_ramp_delay = false;
break;
case PFUZE3000:
reg_has_ramp_delay = (id < PFUZE3000_SWBST);
break;
case PFUZE200:
reg_has_ramp_delay = (id < PFUZE200_SWBST);
break;
case PFUZE100:
default:
reg_has_ramp_delay = (id < PFUZE100_SWBST);
break;
}
if (reg_has_ramp_delay) {
ramp_delay = 12500 / ramp_delay;
ramp_bits = (ramp_delay >> 1) - (ramp_delay >> 3);
ret = regmap_update_bits(pfuze100->regmap,
@ -119,8 +143,9 @@ static int pfuze100_set_ramp_delay(struct regulator_dev *rdev, int ramp_delay)
0xc0, ramp_bits << 6);
if (ret < 0)
dev_err(pfuze100->dev, "ramp failed, err %d\n", ret);
} else
} else {
ret = -EACCES;
}
return ret;
}
@ -142,6 +167,14 @@ static const struct regulator_ops pfuze100_fixed_regulator_ops = {
};
static const struct regulator_ops pfuze100_sw_regulator_ops = {
.list_voltage = regulator_list_voltage_linear,
.set_voltage_sel = regulator_set_voltage_sel_regmap,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
.set_voltage_time_sel = regulator_set_voltage_time_sel,
.set_ramp_delay = pfuze100_set_ramp_delay,
};
static const struct regulator_ops pfuze100_sw_disable_regulator_ops = {
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
.is_enabled = regulator_is_enabled_regmap,
@ -192,13 +225,11 @@ static const struct regulator_ops pfuze100_swb_regulator_ops = {
.vsel_reg = (base) + PFUZE100_VOL_OFFSET, \
.vsel_mask = 0x3f, \
.enable_reg = (base) + PFUZE100_MODE_OFFSET, \
.enable_val = 0xc, \
.disable_val = 0x0, \
.enable_mask = 0xf, \
.enable_time = 500, \
}, \
.stby_reg = (base) + PFUZE100_STANDBY_OFFSET, \
.stby_mask = 0x3f, \
.sw_reg = true, \
}
#define PFUZE100_SWB_REG(_chip, _name, base, mask, voltages) \
@ -361,6 +392,19 @@ static struct pfuze_regulator pfuze3000_regulators[] = {
PFUZE100_VGEN_REG(PFUZE3000, VLDO4, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000),
};
static struct pfuze_regulator pfuze3001_regulators[] = {
PFUZE100_SWB_REG(PFUZE3001, SW1, PFUZE100_SW1ABVOL, 0x1f, pfuze3000_sw1a),
PFUZE100_SWB_REG(PFUZE3001, SW2, PFUZE100_SW2VOL, 0x7, pfuze3000_sw2lo),
PFUZE3000_SW3_REG(PFUZE3001, SW3, PFUZE100_SW3AVOL, 900000, 1650000, 50000),
PFUZE100_SWB_REG(PFUZE3001, VSNVS, PFUZE100_VSNVSVOL, 0x7, pfuze100_vsnvs),
PFUZE100_VGEN_REG(PFUZE3001, VLDO1, PFUZE100_VGEN1VOL, 1800000, 3300000, 100000),
PFUZE100_VGEN_REG(PFUZE3001, VLDO2, PFUZE100_VGEN2VOL, 800000, 1550000, 50000),
PFUZE3000_VCC_REG(PFUZE3001, VCCSD, PFUZE100_VGEN3VOL, 2850000, 3300000, 150000),
PFUZE3000_VCC_REG(PFUZE3001, V33, PFUZE100_VGEN4VOL, 2850000, 3300000, 150000),
PFUZE100_VGEN_REG(PFUZE3001, VLDO3, PFUZE100_VGEN5VOL, 1800000, 3300000, 100000),
PFUZE100_VGEN_REG(PFUZE3001, VLDO4, PFUZE100_VGEN6VOL, 1800000, 3300000, 100000),
};
#ifdef CONFIG_OF
/* PFUZE100 */
static struct of_regulator_match pfuze100_matches[] = {
@ -418,6 +462,21 @@ static struct of_regulator_match pfuze3000_matches[] = {
{ .name = "vldo4", },
};
/* PFUZE3001 */
static struct of_regulator_match pfuze3001_matches[] = {
{ .name = "sw1", },
{ .name = "sw2", },
{ .name = "sw3", },
{ .name = "vsnvs", },
{ .name = "vldo1", },
{ .name = "vldo2", },
{ .name = "vccsd", },
{ .name = "v33", },
{ .name = "vldo3", },
{ .name = "vldo4", },
};
static struct of_regulator_match *pfuze_matches;
static int pfuze_parse_regulators_dt(struct pfuze_chip *chip)
@ -430,6 +489,9 @@ static int pfuze_parse_regulators_dt(struct pfuze_chip *chip)
if (!np)
return -EINVAL;
if (of_property_read_bool(np, "fsl,pfuze-support-disable-sw"))
chip->flags |= PFUZE_FLAG_DISABLE_SW;
parent = of_get_child_by_name(np, "regulators");
if (!parent) {
dev_err(dev, "regulators node not found\n");
@ -437,6 +499,11 @@ static int pfuze_parse_regulators_dt(struct pfuze_chip *chip)
}
switch (chip->chip_id) {
case PFUZE3001:
pfuze_matches = pfuze3001_matches;
ret = of_regulator_match(dev, parent, pfuze3001_matches,
ARRAY_SIZE(pfuze3001_matches));
break;
case PFUZE3000:
pfuze_matches = pfuze3000_matches;
ret = of_regulator_match(dev, parent, pfuze3000_matches,
@ -508,7 +575,8 @@ static int pfuze_identify(struct pfuze_chip *pfuze_chip)
*/
dev_info(pfuze_chip->dev, "Assuming misprogrammed ID=0x8");
} else if ((value & 0x0f) != pfuze_chip->chip_id &&
(value & 0xf0) >> 4 != pfuze_chip->chip_id) {
(value & 0xf0) >> 4 != pfuze_chip->chip_id &&
(value != pfuze_chip->chip_id)) {
/* device id NOT match with your setting */
dev_warn(pfuze_chip->dev, "Illegal ID: %x\n", value);
return -ENODEV;
@ -588,6 +656,13 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
/* use the right regulators after identify the right device */
switch (pfuze_chip->chip_id) {
case PFUZE3001:
pfuze_chip->pfuze_regulators = pfuze3001_regulators;
regulator_num = ARRAY_SIZE(pfuze3001_regulators);
sw_check_start = PFUZE3001_SW2;
sw_check_end = PFUZE3001_SW2;
sw_hi = 1 << 3;
break;
case PFUZE3000:
pfuze_chip->pfuze_regulators = pfuze3000_regulators;
regulator_num = ARRAY_SIZE(pfuze3000_regulators);
@ -611,7 +686,8 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
}
dev_info(&client->dev, "pfuze%s found.\n",
(pfuze_chip->chip_id == PFUZE100) ? "100" :
((pfuze_chip->chip_id == PFUZE200) ? "200" : "3000"));
(((pfuze_chip->chip_id == PFUZE200) ? "200" :
((pfuze_chip->chip_id == PFUZE3000) ? "3000" : "3001"))));
memcpy(pfuze_chip->regulator_descs, pfuze_chip->pfuze_regulators,
sizeof(pfuze_chip->regulator_descs));
@ -636,7 +712,8 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
if (i >= sw_check_start && i <= sw_check_end) {
regmap_read(pfuze_chip->regmap, desc->vsel_reg, &val);
if (val & sw_hi) {
if (pfuze_chip->chip_id == PFUZE3000) {
if (pfuze_chip->chip_id == PFUZE3000 ||
pfuze_chip->chip_id == PFUZE3001) {
desc->volt_table = pfuze3000_sw2hi;
desc->n_voltages = ARRAY_SIZE(pfuze3000_sw2hi);
} else {
@ -647,6 +724,21 @@ static int pfuze100_regulator_probe(struct i2c_client *client,
}
}
/*
* Allow SW regulators to turn off. Checking it trough a flag is
* a workaround to keep the backward compatibility with existing
* old dtb's which may relay on the fact that we didn't disable
* the switched regulator till yet.
*/
if (pfuze_chip->flags & PFUZE_FLAG_DISABLE_SW) {
if (pfuze_chip->regulator_descs[i].sw_reg) {
desc->ops = &pfuze100_sw_disable_regulator_ops;
desc->enable_val = 0x8;
desc->disable_val = 0x0;
desc->enable_time = 500;
}
}
config.dev = &client->dev;
config.init_data = init_data;
config.driver_data = pfuze_chip;
@ -675,5 +767,5 @@ static struct i2c_driver pfuze_driver = {
module_i2c_driver(pfuze_driver);
MODULE_AUTHOR("Robin Gong <b38343@freescale.com>");
MODULE_DESCRIPTION("Regulator Driver for Freescale PFUZE100/200/3000 PMIC");
MODULE_DESCRIPTION("Regulator Driver for Freescale PFUZE100/200/3000/3001 PMIC");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,769 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018, The Linux Foundation. All rights reserved.
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <soc/qcom/cmd-db.h>
#include <soc/qcom/rpmh.h>
#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
/**
* enum rpmh_regulator_type - supported RPMh accelerator types
* %VRM: RPMh VRM accelerator which supports voting on enable, voltage,
* and mode of LDO, SMPS, and BOB type PMIC regulators.
* %XOB: RPMh XOB accelerator which supports voting on the enable state
* of PMIC regulators.
*/
enum rpmh_regulator_type {
VRM,
XOB,
};
#define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0
#define RPMH_REGULATOR_REG_ENABLE 0x4
#define RPMH_REGULATOR_REG_VRM_MODE 0x8
#define PMIC4_LDO_MODE_RETENTION 4
#define PMIC4_LDO_MODE_LPM 5
#define PMIC4_LDO_MODE_HPM 7
#define PMIC4_SMPS_MODE_RETENTION 4
#define PMIC4_SMPS_MODE_PFM 5
#define PMIC4_SMPS_MODE_AUTO 6
#define PMIC4_SMPS_MODE_PWM 7
#define PMIC4_BOB_MODE_PASS 0
#define PMIC4_BOB_MODE_PFM 1
#define PMIC4_BOB_MODE_AUTO 2
#define PMIC4_BOB_MODE_PWM 3
/**
* struct rpmh_vreg_hw_data - RPMh regulator hardware configurations
* @regulator_type: RPMh accelerator type used to manage this
* regulator
* @ops: Pointer to regulator ops callback structure
* @voltage_range: The single range of voltages supported by this
* PMIC regulator type
* @n_voltages: The number of unique voltage set points defined
* by voltage_range
* @hpm_min_load_uA: Minimum load current in microamps that requires
* high power mode (HPM) operation. This is used
* for LDO hardware type regulators only.
* @pmic_mode_map: Array indexed by regulator framework mode
* containing PMIC hardware modes. Must be large
* enough to index all framework modes supported
* by this regulator hardware type.
* @of_map_mode: Maps an RPMH_REGULATOR_MODE_* mode value defined
* in device tree to a regulator framework mode
*/
struct rpmh_vreg_hw_data {
enum rpmh_regulator_type regulator_type;
const struct regulator_ops *ops;
const struct regulator_linear_range voltage_range;
int n_voltages;
int hpm_min_load_uA;
const int *pmic_mode_map;
unsigned int (*of_map_mode)(unsigned int mode);
};
/**
* struct rpmh_vreg - individual RPMh regulator data structure encapsulating a
* single regulator device
* @dev: Device pointer for the top-level PMIC RPMh
* regulator parent device. This is used as a
* handle in RPMh write requests.
* @addr: Base address of the regulator resource within
* an RPMh accelerator
* @rdesc: Regulator descriptor
* @hw_data: PMIC regulator configuration data for this RPMh
* regulator
* @always_wait_for_ack: Boolean flag indicating if a request must always
* wait for an ACK from RPMh before continuing even
* if it corresponds to a strictly lower power
* state (e.g. enabled --> disabled).
* @enabled: Flag indicating if the regulator is enabled or
* not
* @bypassed: Boolean indicating if the regulator is in
* bypass (pass-through) mode or not. This is
* only used by BOB rpmh-regulator resources.
* @voltage_selector: Selector used for get_voltage_sel() and
* set_voltage_sel() callbacks
* @mode: RPMh VRM regulator current framework mode
*/
struct rpmh_vreg {
struct device *dev;
u32 addr;
struct regulator_desc rdesc;
const struct rpmh_vreg_hw_data *hw_data;
bool always_wait_for_ack;
int enabled;
bool bypassed;
int voltage_selector;
unsigned int mode;
};
/**
* struct rpmh_vreg_init_data - initialization data for an RPMh regulator
* @name: Name for the regulator which also corresponds
* to the device tree subnode name of the regulator
* @resource_name: RPMh regulator resource name format string.
* This must include exactly one field: '%s' which
* is filled at run-time with the PMIC ID provided
* by device tree property qcom,pmic-id. Example:
* "ldo%s1" for RPMh resource "ldoa1".
* @supply_name: Parent supply regulator name
* @hw_data: Configuration data for this PMIC regulator type
*/
struct rpmh_vreg_init_data {
const char *name;
const char *resource_name;
const char *supply_name;
const struct rpmh_vreg_hw_data *hw_data;
};
/**
* rpmh_regulator_send_request() - send the request to RPMh
* @vreg: Pointer to the RPMh regulator
* @cmd: Pointer to the RPMh command to send
* @wait_for_ack: Boolean indicating if execution must wait until the
* request has been acknowledged as complete
*
* Return: 0 on success, errno on failure
*/
static int rpmh_regulator_send_request(struct rpmh_vreg *vreg,
struct tcs_cmd *cmd, bool wait_for_ack)
{
int ret;
if (wait_for_ack || vreg->always_wait_for_ack)
ret = rpmh_write(vreg->dev, RPMH_ACTIVE_ONLY_STATE, cmd, 1);
else
ret = rpmh_write_async(vreg->dev, RPMH_ACTIVE_ONLY_STATE, cmd,
1);
return ret;
}
static int _rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev,
unsigned int selector, bool wait_for_ack)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
struct tcs_cmd cmd = {
.addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE,
};
int ret;
/* VRM voltage control register is set with voltage in millivolts. */
cmd.data = DIV_ROUND_UP(regulator_list_voltage_linear_range(rdev,
selector), 1000);
ret = rpmh_regulator_send_request(vreg, &cmd, wait_for_ack);
if (!ret)
vreg->voltage_selector = selector;
return ret;
}
static int rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev,
unsigned int selector)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
if (vreg->enabled == -EINVAL) {
/*
* Cache the voltage and send it later when the regulator is
* enabled or disabled.
*/
vreg->voltage_selector = selector;
return 0;
}
return _rpmh_regulator_vrm_set_voltage_sel(rdev, selector,
selector > vreg->voltage_selector);
}
static int rpmh_regulator_vrm_get_voltage_sel(struct regulator_dev *rdev)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
return vreg->voltage_selector;
}
static int rpmh_regulator_is_enabled(struct regulator_dev *rdev)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
return vreg->enabled;
}
static int rpmh_regulator_set_enable_state(struct regulator_dev *rdev,
bool enable)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
struct tcs_cmd cmd = {
.addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE,
.data = enable,
};
int ret;
if (vreg->enabled == -EINVAL &&
vreg->voltage_selector != -ENOTRECOVERABLE) {
ret = _rpmh_regulator_vrm_set_voltage_sel(rdev,
vreg->voltage_selector, true);
if (ret < 0)
return ret;
}
ret = rpmh_regulator_send_request(vreg, &cmd, enable);
if (!ret)
vreg->enabled = enable;
return ret;
}
static int rpmh_regulator_enable(struct regulator_dev *rdev)
{
return rpmh_regulator_set_enable_state(rdev, true);
}
static int rpmh_regulator_disable(struct regulator_dev *rdev)
{
return rpmh_regulator_set_enable_state(rdev, false);
}
static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg,
unsigned int mode, bool bypassed)
{
struct tcs_cmd cmd = {
.addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE,
};
int pmic_mode;
if (mode > REGULATOR_MODE_STANDBY)
return -EINVAL;
pmic_mode = vreg->hw_data->pmic_mode_map[mode];
if (pmic_mode < 0)
return pmic_mode;
if (bypassed)
cmd.data = PMIC4_BOB_MODE_PASS;
else
cmd.data = pmic_mode;
return rpmh_regulator_send_request(vreg, &cmd, true);
}
static int rpmh_regulator_vrm_set_mode(struct regulator_dev *rdev,
unsigned int mode)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
int ret;
if (mode == vreg->mode)
return 0;
ret = rpmh_regulator_vrm_set_mode_bypass(vreg, mode, vreg->bypassed);
if (!ret)
vreg->mode = mode;
return ret;
}
static unsigned int rpmh_regulator_vrm_get_mode(struct regulator_dev *rdev)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
return vreg->mode;
}
/**
* rpmh_regulator_vrm_set_load() - set the regulator mode based upon the load
* current requested
* @rdev: Regulator device pointer for the rpmh-regulator
* @load_uA: Aggregated load current in microamps
*
* This function is used in the regulator_ops for VRM type RPMh regulator
* devices.
*
* Return: 0 on success, errno on failure
*/
static int rpmh_regulator_vrm_set_load(struct regulator_dev *rdev, int load_uA)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
unsigned int mode;
if (load_uA >= vreg->hw_data->hpm_min_load_uA)
mode = REGULATOR_MODE_NORMAL;
else
mode = REGULATOR_MODE_IDLE;
return rpmh_regulator_vrm_set_mode(rdev, mode);
}
static int rpmh_regulator_vrm_set_bypass(struct regulator_dev *rdev,
bool enable)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
int ret;
if (vreg->bypassed == enable)
return 0;
ret = rpmh_regulator_vrm_set_mode_bypass(vreg, vreg->mode, enable);
if (!ret)
vreg->bypassed = enable;
return ret;
}
static int rpmh_regulator_vrm_get_bypass(struct regulator_dev *rdev,
bool *enable)
{
struct rpmh_vreg *vreg = rdev_get_drvdata(rdev);
*enable = vreg->bypassed;
return 0;
}
static const struct regulator_ops rpmh_regulator_vrm_ops = {
.enable = rpmh_regulator_enable,
.disable = rpmh_regulator_disable,
.is_enabled = rpmh_regulator_is_enabled,
.set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel,
.get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel,
.list_voltage = regulator_list_voltage_linear_range,
.set_mode = rpmh_regulator_vrm_set_mode,
.get_mode = rpmh_regulator_vrm_get_mode,
};
static const struct regulator_ops rpmh_regulator_vrm_drms_ops = {
.enable = rpmh_regulator_enable,
.disable = rpmh_regulator_disable,
.is_enabled = rpmh_regulator_is_enabled,
.set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel,
.get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel,
.list_voltage = regulator_list_voltage_linear_range,
.set_mode = rpmh_regulator_vrm_set_mode,
.get_mode = rpmh_regulator_vrm_get_mode,
.set_load = rpmh_regulator_vrm_set_load,
};
static const struct regulator_ops rpmh_regulator_vrm_bypass_ops = {
.enable = rpmh_regulator_enable,
.disable = rpmh_regulator_disable,
.is_enabled = rpmh_regulator_is_enabled,
.set_voltage_sel = rpmh_regulator_vrm_set_voltage_sel,
.get_voltage_sel = rpmh_regulator_vrm_get_voltage_sel,
.list_voltage = regulator_list_voltage_linear_range,
.set_mode = rpmh_regulator_vrm_set_mode,
.get_mode = rpmh_regulator_vrm_get_mode,
.set_bypass = rpmh_regulator_vrm_set_bypass,
.get_bypass = rpmh_regulator_vrm_get_bypass,
};
static const struct regulator_ops rpmh_regulator_xob_ops = {
.enable = rpmh_regulator_enable,
.disable = rpmh_regulator_disable,
.is_enabled = rpmh_regulator_is_enabled,
};
/**
* rpmh_regulator_init_vreg() - initialize all attributes of an rpmh-regulator
* vreg: Pointer to the individual rpmh-regulator resource
* dev: Pointer to the top level rpmh-regulator PMIC device
* node: Pointer to the individual rpmh-regulator resource
* device node
* pmic_id: String used to identify the top level rpmh-regulator
* PMIC device on the board
* pmic_rpmh_data: Pointer to a null-terminated array of rpmh-regulator
* resources defined for the top level PMIC device
*
* Return: 0 on success, errno on failure
*/
static int rpmh_regulator_init_vreg(struct rpmh_vreg *vreg, struct device *dev,
struct device_node *node, const char *pmic_id,
const struct rpmh_vreg_init_data *pmic_rpmh_data)
{
struct regulator_config reg_config = {};
char rpmh_resource_name[20] = "";
const struct rpmh_vreg_init_data *rpmh_data;
struct regulator_init_data *init_data;
struct regulator_dev *rdev;
int ret;
vreg->dev = dev;
for (rpmh_data = pmic_rpmh_data; rpmh_data->name; rpmh_data++)
if (!strcmp(rpmh_data->name, node->name))
break;
if (!rpmh_data->name) {
dev_err(dev, "Unknown regulator %s\n", node->name);
return -EINVAL;
}
scnprintf(rpmh_resource_name, sizeof(rpmh_resource_name),
rpmh_data->resource_name, pmic_id);
vreg->addr = cmd_db_read_addr(rpmh_resource_name);
if (!vreg->addr) {
dev_err(dev, "%s: could not find RPMh address for resource %s\n",
node->name, rpmh_resource_name);
return -ENODEV;
}
vreg->rdesc.name = rpmh_data->name;
vreg->rdesc.supply_name = rpmh_data->supply_name;
vreg->hw_data = rpmh_data->hw_data;
vreg->enabled = -EINVAL;
vreg->voltage_selector = -ENOTRECOVERABLE;
vreg->mode = REGULATOR_MODE_INVALID;
if (rpmh_data->hw_data->n_voltages) {
vreg->rdesc.linear_ranges = &rpmh_data->hw_data->voltage_range;
vreg->rdesc.n_linear_ranges = 1;
vreg->rdesc.n_voltages = rpmh_data->hw_data->n_voltages;
}
vreg->always_wait_for_ack = of_property_read_bool(node,
"qcom,always-wait-for-ack");
vreg->rdesc.owner = THIS_MODULE;
vreg->rdesc.type = REGULATOR_VOLTAGE;
vreg->rdesc.ops = vreg->hw_data->ops;
vreg->rdesc.of_map_mode = vreg->hw_data->of_map_mode;
init_data = of_get_regulator_init_data(dev, node, &vreg->rdesc);
if (!init_data)
return -ENOMEM;
if (rpmh_data->hw_data->regulator_type == XOB &&
init_data->constraints.min_uV &&
init_data->constraints.min_uV == init_data->constraints.max_uV) {
vreg->rdesc.fixed_uV = init_data->constraints.min_uV;
vreg->rdesc.n_voltages = 1;
}
reg_config.dev = dev;
reg_config.init_data = init_data;
reg_config.of_node = node;
reg_config.driver_data = vreg;
rdev = devm_regulator_register(dev, &vreg->rdesc, &reg_config);
if (IS_ERR(rdev)) {
ret = PTR_ERR(rdev);
dev_err(dev, "%s: devm_regulator_register() failed, ret=%d\n",
node->name, ret);
return ret;
}
dev_dbg(dev, "%s regulator registered for RPMh resource %s @ 0x%05X\n",
node->name, rpmh_resource_name, vreg->addr);
return 0;
}
static const int pmic_mode_map_pmic4_ldo[REGULATOR_MODE_STANDBY + 1] = {
[REGULATOR_MODE_INVALID] = -EINVAL,
[REGULATOR_MODE_STANDBY] = PMIC4_LDO_MODE_RETENTION,
[REGULATOR_MODE_IDLE] = PMIC4_LDO_MODE_LPM,
[REGULATOR_MODE_NORMAL] = PMIC4_LDO_MODE_HPM,
[REGULATOR_MODE_FAST] = -EINVAL,
};
static unsigned int rpmh_regulator_pmic4_ldo_of_map_mode(unsigned int rpmh_mode)
{
unsigned int mode;
switch (rpmh_mode) {
case RPMH_REGULATOR_MODE_HPM:
mode = REGULATOR_MODE_NORMAL;
break;
case RPMH_REGULATOR_MODE_LPM:
mode = REGULATOR_MODE_IDLE;
break;
case RPMH_REGULATOR_MODE_RET:
mode = REGULATOR_MODE_STANDBY;
break;
default:
mode = REGULATOR_MODE_INVALID;
}
return mode;
}
static const int pmic_mode_map_pmic4_smps[REGULATOR_MODE_STANDBY + 1] = {
[REGULATOR_MODE_INVALID] = -EINVAL,
[REGULATOR_MODE_STANDBY] = PMIC4_SMPS_MODE_RETENTION,
[REGULATOR_MODE_IDLE] = PMIC4_SMPS_MODE_PFM,
[REGULATOR_MODE_NORMAL] = PMIC4_SMPS_MODE_AUTO,
[REGULATOR_MODE_FAST] = PMIC4_SMPS_MODE_PWM,
};
static unsigned int
rpmh_regulator_pmic4_smps_of_map_mode(unsigned int rpmh_mode)
{
unsigned int mode;
switch (rpmh_mode) {
case RPMH_REGULATOR_MODE_HPM:
mode = REGULATOR_MODE_FAST;
break;
case RPMH_REGULATOR_MODE_AUTO:
mode = REGULATOR_MODE_NORMAL;
break;
case RPMH_REGULATOR_MODE_LPM:
mode = REGULATOR_MODE_IDLE;
break;
case RPMH_REGULATOR_MODE_RET:
mode = REGULATOR_MODE_STANDBY;
break;
default:
mode = REGULATOR_MODE_INVALID;
}
return mode;
}
static const int pmic_mode_map_pmic4_bob[REGULATOR_MODE_STANDBY + 1] = {
[REGULATOR_MODE_INVALID] = -EINVAL,
[REGULATOR_MODE_STANDBY] = -EINVAL,
[REGULATOR_MODE_IDLE] = PMIC4_BOB_MODE_PFM,
[REGULATOR_MODE_NORMAL] = PMIC4_BOB_MODE_AUTO,
[REGULATOR_MODE_FAST] = PMIC4_BOB_MODE_PWM,
};
static unsigned int rpmh_regulator_pmic4_bob_of_map_mode(unsigned int rpmh_mode)
{
unsigned int mode;
switch (rpmh_mode) {
case RPMH_REGULATOR_MODE_HPM:
mode = REGULATOR_MODE_FAST;
break;
case RPMH_REGULATOR_MODE_AUTO:
mode = REGULATOR_MODE_NORMAL;
break;
case RPMH_REGULATOR_MODE_LPM:
mode = REGULATOR_MODE_IDLE;
break;
default:
mode = REGULATOR_MODE_INVALID;
}
return mode;
}
static const struct rpmh_vreg_hw_data pmic4_pldo = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_drms_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(1664000, 0, 255, 8000),
.n_voltages = 256,
.hpm_min_load_uA = 10000,
.pmic_mode_map = pmic_mode_map_pmic4_ldo,
.of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_pldo_lv = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_drms_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(1256000, 0, 127, 8000),
.n_voltages = 128,
.hpm_min_load_uA = 10000,
.pmic_mode_map = pmic_mode_map_pmic4_ldo,
.of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_nldo = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_drms_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(312000, 0, 127, 8000),
.n_voltages = 128,
.hpm_min_load_uA = 30000,
.pmic_mode_map = pmic_mode_map_pmic4_ldo,
.of_map_mode = rpmh_regulator_pmic4_ldo_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_hfsmps3 = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(320000, 0, 215, 8000),
.n_voltages = 216,
.pmic_mode_map = pmic_mode_map_pmic4_smps,
.of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_ftsmps426 = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(320000, 0, 258, 4000),
.n_voltages = 259,
.pmic_mode_map = pmic_mode_map_pmic4_smps,
.of_map_mode = rpmh_regulator_pmic4_smps_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_bob = {
.regulator_type = VRM,
.ops = &rpmh_regulator_vrm_bypass_ops,
.voltage_range = REGULATOR_LINEAR_RANGE(1824000, 0, 83, 32000),
.n_voltages = 84,
.pmic_mode_map = pmic_mode_map_pmic4_bob,
.of_map_mode = rpmh_regulator_pmic4_bob_of_map_mode,
};
static const struct rpmh_vreg_hw_data pmic4_lvs = {
.regulator_type = XOB,
.ops = &rpmh_regulator_xob_ops,
/* LVS hardware does not support voltage or mode configuration. */
};
#define RPMH_VREG(_name, _resource_name, _hw_data, _supply_name) \
{ \
.name = _name, \
.resource_name = _resource_name, \
.hw_data = _hw_data, \
.supply_name = _supply_name, \
}
static const struct rpmh_vreg_init_data pm8998_vreg_data[] = {
RPMH_VREG("smps1", "smp%s1", &pmic4_ftsmps426, "vdd-s1"),
RPMH_VREG("smps2", "smp%s2", &pmic4_ftsmps426, "vdd-s2"),
RPMH_VREG("smps3", "smp%s3", &pmic4_hfsmps3, "vdd-s3"),
RPMH_VREG("smps4", "smp%s4", &pmic4_hfsmps3, "vdd-s4"),
RPMH_VREG("smps5", "smp%s5", &pmic4_hfsmps3, "vdd-s5"),
RPMH_VREG("smps6", "smp%s6", &pmic4_ftsmps426, "vdd-s6"),
RPMH_VREG("smps7", "smp%s7", &pmic4_ftsmps426, "vdd-s7"),
RPMH_VREG("smps8", "smp%s8", &pmic4_ftsmps426, "vdd-s8"),
RPMH_VREG("smps9", "smp%s9", &pmic4_ftsmps426, "vdd-s9"),
RPMH_VREG("smps10", "smp%s10", &pmic4_ftsmps426, "vdd-s10"),
RPMH_VREG("smps11", "smp%s11", &pmic4_ftsmps426, "vdd-s11"),
RPMH_VREG("smps12", "smp%s12", &pmic4_ftsmps426, "vdd-s12"),
RPMH_VREG("smps13", "smp%s13", &pmic4_ftsmps426, "vdd-s13"),
RPMH_VREG("ldo1", "ldo%s1", &pmic4_nldo, "vdd-l1-l27"),
RPMH_VREG("ldo2", "ldo%s2", &pmic4_nldo, "vdd-l2-l8-l17"),
RPMH_VREG("ldo3", "ldo%s3", &pmic4_nldo, "vdd-l3-l11"),
RPMH_VREG("ldo4", "ldo%s4", &pmic4_nldo, "vdd-l4-l5"),
RPMH_VREG("ldo5", "ldo%s5", &pmic4_nldo, "vdd-l4-l5"),
RPMH_VREG("ldo6", "ldo%s6", &pmic4_pldo, "vdd-l6"),
RPMH_VREG("ldo7", "ldo%s7", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"),
RPMH_VREG("ldo8", "ldo%s8", &pmic4_nldo, "vdd-l2-l8-l17"),
RPMH_VREG("ldo9", "ldo%s9", &pmic4_pldo, "vdd-l9"),
RPMH_VREG("ldo10", "ldo%s10", &pmic4_pldo, "vdd-l10-l23-l25"),
RPMH_VREG("ldo11", "ldo%s11", &pmic4_nldo, "vdd-l3-l11"),
RPMH_VREG("ldo12", "ldo%s12", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"),
RPMH_VREG("ldo13", "ldo%s13", &pmic4_pldo, "vdd-l13-l19-l21"),
RPMH_VREG("ldo14", "ldo%s14", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"),
RPMH_VREG("ldo15", "ldo%s15", &pmic4_pldo_lv, "vdd-l7-l12-l14-l15"),
RPMH_VREG("ldo16", "ldo%s16", &pmic4_pldo, "vdd-l16-l28"),
RPMH_VREG("ldo17", "ldo%s17", &pmic4_nldo, "vdd-l2-l8-l17"),
RPMH_VREG("ldo18", "ldo%s18", &pmic4_pldo, "vdd-l18-l22"),
RPMH_VREG("ldo19", "ldo%s19", &pmic4_pldo, "vdd-l13-l19-l21"),
RPMH_VREG("ldo20", "ldo%s20", &pmic4_pldo, "vdd-l20-l24"),
RPMH_VREG("ldo21", "ldo%s21", &pmic4_pldo, "vdd-l13-l19-l21"),
RPMH_VREG("ldo22", "ldo%s22", &pmic4_pldo, "vdd-l18-l22"),
RPMH_VREG("ldo23", "ldo%s23", &pmic4_pldo, "vdd-l10-l23-l25"),
RPMH_VREG("ldo24", "ldo%s24", &pmic4_pldo, "vdd-l20-l24"),
RPMH_VREG("ldo25", "ldo%s25", &pmic4_pldo, "vdd-l10-l23-l25"),
RPMH_VREG("ldo26", "ldo%s26", &pmic4_nldo, "vdd-l26"),
RPMH_VREG("ldo27", "ldo%s27", &pmic4_nldo, "vdd-l1-l27"),
RPMH_VREG("ldo28", "ldo%s28", &pmic4_pldo, "vdd-l16-l28"),
RPMH_VREG("lvs1", "vs%s1", &pmic4_lvs, "vin-lvs-1-2"),
RPMH_VREG("lvs2", "vs%s2", &pmic4_lvs, "vin-lvs-1-2"),
{},
};
static const struct rpmh_vreg_init_data pmi8998_vreg_data[] = {
RPMH_VREG("bob", "bob%s1", &pmic4_bob, "vdd-bob"),
{},
};
static const struct rpmh_vreg_init_data pm8005_vreg_data[] = {
RPMH_VREG("smps1", "smp%s1", &pmic4_ftsmps426, "vdd-s1"),
RPMH_VREG("smps2", "smp%s2", &pmic4_ftsmps426, "vdd-s2"),
RPMH_VREG("smps3", "smp%s3", &pmic4_ftsmps426, "vdd-s3"),
RPMH_VREG("smps4", "smp%s4", &pmic4_ftsmps426, "vdd-s4"),
{},
};
static int rpmh_regulator_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct rpmh_vreg_init_data *vreg_data;
struct device_node *node;
struct rpmh_vreg *vreg;
const char *pmic_id;
int ret;
vreg_data = of_device_get_match_data(dev);
if (!vreg_data)
return -ENODEV;
ret = of_property_read_string(dev->of_node, "qcom,pmic-id", &pmic_id);
if (ret < 0) {
dev_err(dev, "qcom,pmic-id missing in DT node\n");
return ret;
}
for_each_available_child_of_node(dev->of_node, node) {
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
if (!vreg) {
of_node_put(node);
return -ENOMEM;
}
ret = rpmh_regulator_init_vreg(vreg, dev, node, pmic_id,
vreg_data);
if (ret < 0) {
of_node_put(node);
return ret;
}
}
return 0;
}
static const struct of_device_id rpmh_regulator_match_table[] = {
{
.compatible = "qcom,pm8998-rpmh-regulators",
.data = pm8998_vreg_data,
},
{
.compatible = "qcom,pmi8998-rpmh-regulators",
.data = pmi8998_vreg_data,
},
{
.compatible = "qcom,pm8005-rpmh-regulators",
.data = pm8005_vreg_data,
},
{}
};
MODULE_DEVICE_TABLE(of, rpmh_regulator_match_table);
static struct platform_driver rpmh_regulator_driver = {
.driver = {
.name = "qcom-rpmh-regulator",
.of_match_table = of_match_ptr(rpmh_regulator_match_table),
},
.probe = rpmh_regulator_probe,
};
module_platform_driver(rpmh_regulator_driver);
MODULE_DESCRIPTION("Qualcomm RPMh regulator driver");
MODULE_LICENSE("GPL v2");

View File

@ -1060,7 +1060,7 @@ static irqreturn_t spmi_regulator_vs_ocp_isr(int irq, void *data)
#define SAW3_AVS_CTL_TGGL_MASK 0x8000000
#define SAW3_AVS_CTL_CLEAR_MASK 0x7efc00
static struct regmap *saw_regmap = NULL;
static struct regmap *saw_regmap;
static void spmi_saw_set_vdd(void *data)
{
@ -1728,7 +1728,7 @@ static const struct spmi_regulator_data pmi8994_regulators[] = {
{ "s2", 0x1700, "vdd_s2", },
{ "s3", 0x1a00, "vdd_s3", },
{ "l1", 0x4000, "vdd_l1", },
{ }
{ }
};
static const struct of_device_id qcom_spmi_regulator_match[] = {
@ -1752,7 +1752,8 @@ static int qcom_spmi_regulator_probe(struct platform_device *pdev)
const char *name;
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
struct device_node *syscon;
struct device_node *syscon, *reg_node;
struct property *reg_prop;
int ret, lenp;
struct list_head *vreg_list;
@ -1774,16 +1775,19 @@ static int qcom_spmi_regulator_probe(struct platform_device *pdev)
syscon = of_parse_phandle(node, "qcom,saw-reg", 0);
saw_regmap = syscon_node_to_regmap(syscon);
of_node_put(syscon);
if (IS_ERR(regmap))
if (IS_ERR(saw_regmap))
dev_err(dev, "ERROR reading SAW regmap\n");
}
for (reg = match->data; reg->name; reg++) {
if (saw_regmap && \
of_find_property(of_find_node_by_name(node, reg->name), \
"qcom,saw-slave", &lenp)) {
continue;
if (saw_regmap) {
reg_node = of_get_child_by_name(node, reg->name);
reg_prop = of_find_property(reg_node, "qcom,saw-slave",
&lenp);
of_node_put(reg_node);
if (reg_prop)
continue;
}
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
@ -1816,13 +1820,17 @@ static int qcom_spmi_regulator_probe(struct platform_device *pdev)
if (ret)
continue;
if (saw_regmap && \
of_find_property(of_find_node_by_name(node, reg->name), \
"qcom,saw-leader", &lenp)) {
spmi_saw_ops = *(vreg->desc.ops);
spmi_saw_ops.set_voltage_sel = \
spmi_regulator_saw_set_voltage;
vreg->desc.ops = &spmi_saw_ops;
if (saw_regmap) {
reg_node = of_get_child_by_name(node, reg->name);
reg_prop = of_find_property(reg_node, "qcom,saw-leader",
&lenp);
of_node_put(reg_node);
if (reg_prop) {
spmi_saw_ops = *(vreg->desc.ops);
spmi_saw_ops.set_voltage_sel =
spmi_regulator_saw_set_voltage;
vreg->desc.ops = &spmi_saw_ops;
}
}
config.dev = dev;

View File

@ -1,13 +1,7 @@
/*
* Copyright (c) 2013 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (c) 2013 Samsung Electronics Co., Ltd
// http://www.samsung.com
#include <linux/bug.h>
#include <linux/err.h>

View File

@ -1,20 +1,7 @@
/*
* s2mps11.c
*
* Copyright (c) 2012-2014 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* 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.
*
*/
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (c) 2012-2014 Samsung Electronics Co., Ltd
// http://www.samsung.com
#include <linux/bug.h>
#include <linux/err.h>

View File

@ -1,15 +1,7 @@
/*
* s5m8767.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
*/
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright (c) 2011 Samsung Electronics Co., Ltd
// http://www.samsung.com
#include <linux/err.h>
#include <linux/of_gpio.h>

View File

@ -232,6 +232,8 @@ static int tps65217_regulator_probe(struct platform_device *pdev)
tps->strobes = devm_kcalloc(&pdev->dev,
TPS65217_NUM_REGULATOR, sizeof(u8),
GFP_KERNEL);
if (!tps->strobes)
return -ENOMEM;
platform_set_drvdata(pdev, tps);

View File

@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0
//
// Regulator controller driver for UniPhier SoC
// Copyright 2018 Socionext Inc.
// Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/reset.h>
#define MAX_CLKS 2
#define MAX_RSTS 2
struct uniphier_regulator_soc_data {
int nclks;
const char * const *clock_names;
int nrsts;
const char * const *reset_names;
const struct regulator_desc *desc;
const struct regmap_config *regconf;
};
struct uniphier_regulator_priv {
struct clk_bulk_data clk[MAX_CLKS];
struct reset_control *rst[MAX_RSTS];
const struct uniphier_regulator_soc_data *data;
};
static struct regulator_ops uniphier_regulator_ops = {
.enable = regulator_enable_regmap,
.disable = regulator_disable_regmap,
.is_enabled = regulator_is_enabled_regmap,
};
static int uniphier_regulator_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct uniphier_regulator_priv *priv;
struct regulator_config config = { };
struct regulator_dev *rdev;
struct regmap *regmap;
struct resource *res;
void __iomem *base;
const char *name;
int i, ret, nr;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->data = of_device_get_match_data(dev);
if (WARN_ON(!priv->data))
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
for (i = 0; i < priv->data->nclks; i++)
priv->clk[i].id = priv->data->clock_names[i];
ret = devm_clk_bulk_get(dev, priv->data->nclks, priv->clk);
if (ret)
return ret;
for (i = 0; i < priv->data->nrsts; i++) {
name = priv->data->reset_names[i];
priv->rst[i] = devm_reset_control_get_shared(dev, name);
if (IS_ERR(priv->rst[i]))
return PTR_ERR(priv->rst[i]);
}
ret = clk_bulk_prepare_enable(priv->data->nclks, priv->clk);
if (ret)
return ret;
for (nr = 0; nr < priv->data->nrsts; nr++) {
ret = reset_control_deassert(priv->rst[nr]);
if (ret)
goto out_rst_assert;
}
regmap = devm_regmap_init_mmio(dev, base, priv->data->regconf);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
config.dev = dev;
config.driver_data = priv;
config.of_node = dev->of_node;
config.regmap = regmap;
config.init_data = of_get_regulator_init_data(dev, dev->of_node,
priv->data->desc);
rdev = devm_regulator_register(dev, priv->data->desc, &config);
if (IS_ERR(rdev)) {
ret = PTR_ERR(rdev);
goto out_rst_assert;
}
platform_set_drvdata(pdev, priv);
return 0;
out_rst_assert:
while (nr--)
reset_control_assert(priv->rst[nr]);
clk_bulk_disable_unprepare(priv->data->nclks, priv->clk);
return ret;
}
static int uniphier_regulator_remove(struct platform_device *pdev)
{
struct uniphier_regulator_priv *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < priv->data->nrsts; i++)
reset_control_assert(priv->rst[i]);
clk_bulk_disable_unprepare(priv->data->nclks, priv->clk);
return 0;
}
/* USB3 controller data */
#define USB3VBUS_OFFSET 0x0
#define USB3VBUS_REG BIT(4)
#define USB3VBUS_REG_EN BIT(3)
static const struct regulator_desc uniphier_usb3_regulator_desc = {
.name = "vbus",
.of_match = of_match_ptr("vbus"),
.ops = &uniphier_regulator_ops,
.type = REGULATOR_VOLTAGE,
.owner = THIS_MODULE,
.enable_reg = USB3VBUS_OFFSET,
.enable_mask = USB3VBUS_REG_EN | USB3VBUS_REG,
.enable_val = USB3VBUS_REG_EN | USB3VBUS_REG,
.disable_val = USB3VBUS_REG_EN,
};
static const struct regmap_config uniphier_usb3_regulator_regconf = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = 1,
};
static const char * const uniphier_pro4_clock_reset_names[] = {
"gio", "link",
};
static const struct uniphier_regulator_soc_data uniphier_pro4_usb3_data = {
.nclks = ARRAY_SIZE(uniphier_pro4_clock_reset_names),
.clock_names = uniphier_pro4_clock_reset_names,
.nrsts = ARRAY_SIZE(uniphier_pro4_clock_reset_names),
.reset_names = uniphier_pro4_clock_reset_names,
.desc = &uniphier_usb3_regulator_desc,
.regconf = &uniphier_usb3_regulator_regconf,
};
static const char * const uniphier_pxs2_clock_reset_names[] = {
"link",
};
static const struct uniphier_regulator_soc_data uniphier_pxs2_usb3_data = {
.nclks = ARRAY_SIZE(uniphier_pxs2_clock_reset_names),
.clock_names = uniphier_pxs2_clock_reset_names,
.nrsts = ARRAY_SIZE(uniphier_pxs2_clock_reset_names),
.reset_names = uniphier_pxs2_clock_reset_names,
.desc = &uniphier_usb3_regulator_desc,
.regconf = &uniphier_usb3_regulator_regconf,
};
static const struct of_device_id uniphier_regulator_match[] = {
/* USB VBUS */
{
.compatible = "socionext,uniphier-pro4-usb3-regulator",
.data = &uniphier_pro4_usb3_data,
},
{
.compatible = "socionext,uniphier-pxs2-usb3-regulator",
.data = &uniphier_pxs2_usb3_data,
},
{
.compatible = "socionext,uniphier-ld20-usb3-regulator",
.data = &uniphier_pxs2_usb3_data,
},
{
.compatible = "socionext,uniphier-pxs3-usb3-regulator",
.data = &uniphier_pxs2_usb3_data,
},
{ /* Sentinel */ },
};
static struct platform_driver uniphier_regulator_driver = {
.probe = uniphier_regulator_probe,
.remove = uniphier_regulator_remove,
.driver = {
.name = "uniphier-regulator",
.of_match_table = uniphier_regulator_match,
},
};
module_platform_driver(uniphier_regulator_driver);
MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>");
MODULE_DESCRIPTION("UniPhier Regulator Controller Driver");
MODULE_LICENSE("GPL");

View File

@ -40,6 +40,23 @@ config QCOM_GSBI
functions for connecting the underlying serial UART, SPI, and I2C
devices to the output pins.
config QCOM_LLCC
tristate "Qualcomm Technologies, Inc. LLCC driver"
depends on ARCH_QCOM
help
Qualcomm Technologies, Inc. platform specific
Last Level Cache Controller(LLCC) driver. This provides interfaces
to clients that use the LLCC. Say yes here to enable LLCC slice
driver.
config QCOM_SDM845_LLCC
tristate "Qualcomm Technologies, Inc. SDM845 LLCC driver"
depends on QCOM_LLCC
help
Say yes here to enable the LLCC driver for SDM845. This provides
data required to configure LLCC so that clients can start using the
LLCC slices.
config QCOM_MDT_LOADER
tristate
select QCOM_SCM
@ -75,6 +92,16 @@ config QCOM_RMTFS_MEM
Say y here if you intend to boot the modem remoteproc.
config QCOM_RPMH
bool "Qualcomm RPM-Hardened (RPMH) Communication"
depends on ARCH_QCOM && ARM64 && OF || COMPILE_TEST
help
Support for communication with the hardened-RPM blocks in
Qualcomm Technologies Inc (QTI) SoCs. RPMH communication uses an
internal bus to transmit state requests for shared resources. A set
of hardware components aggregate requests for these resources and
help apply the aggregated state on the resource.
config QCOM_SMEM
tristate "Qualcomm Shared Memory Manager (SMEM)"
depends on ARCH_QCOM

View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS_rpmh-rsc.o := -I$(src)
obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o
obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o
@ -8,6 +9,9 @@ obj-$(CONFIG_QCOM_PM) += spm.o
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
qmi_helpers-y += qmi_encdec.o qmi_interface.o
obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o
obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
qcom_rpmh-y += rpmh-rsc.o
qcom_rpmh-y += rpmh.o
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) += smem.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
@ -15,3 +19,5 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_APR) += apr.o
obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o

View File

@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/soc/qcom/llcc-qcom.h>
/*
* SCT(System Cache Table) entry contains of the following members:
* usecase_id: Unique id for the client's use case
* slice_id: llcc slice id for each client
* max_cap: The maximum capacity of the cache slice provided in KB
* priority: Priority of the client used to select victim line for replacement
* fixed_size: Boolean indicating if the slice has a fixed capacity
* bonus_ways: Bonus ways are additional ways to be used for any slice,
* if client ends up using more than reserved cache ways. Bonus
* ways are allocated only if they are not reserved for some
* other client.
* res_ways: Reserved ways for the cache slice, the reserved ways cannot
* be used by any other client than the one its assigned to.
* cache_mode: Each slice operates as a cache, this controls the mode of the
* slice: normal or TCM(Tightly Coupled Memory)
* probe_target_ways: Determines what ways to probe for access hit. When
* configured to 1 only bonus and reserved ways are probed.
* When configured to 0 all ways in llcc are probed.
* dis_cap_alloc: Disable capacity based allocation for a client
* retain_on_pc: If this bit is set and client has maintained active vote
* then the ways assigned to this client are not flushed on power
* collapse.
* activate_on_init: Activate the slice immediately after the SCT is programmed
*/
#define SCT_ENTRY(uid, sid, mc, p, fs, bway, rway, cmod, ptw, dca, rp, a) \
{ \
.usecase_id = uid, \
.slice_id = sid, \
.max_cap = mc, \
.priority = p, \
.fixed_size = fs, \
.bonus_ways = bway, \
.res_ways = rway, \
.cache_mode = cmod, \
.probe_target_ways = ptw, \
.dis_cap_alloc = dca, \
.retain_on_pc = rp, \
.activate_on_init = a, \
}
static struct llcc_slice_config sdm845_data[] = {
SCT_ENTRY(LLCC_CPUSS, 1, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 1),
SCT_ENTRY(LLCC_VIDSC0, 2, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_VIDSC1, 3, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_ROTATOR, 4, 563, 2, 1, 0x0, 0x00e, 2, 0, 1, 1, 0),
SCT_ENTRY(LLCC_VOICE, 5, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_AUDIO, 6, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_MDMHPGRW, 7, 1024, 2, 0, 0xfc, 0xf00, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_MDM, 8, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_CMPT, 10, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_GPUHTW, 11, 512, 1, 1, 0xc, 0x0, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_GPU, 12, 2304, 1, 0, 0xff0, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_MMUHWT, 13, 256, 2, 0, 0x0, 0x1, 0, 0, 1, 0, 1),
SCT_ENTRY(LLCC_CMPTDMA, 15, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_DISP, 16, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_VIDFW, 17, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_MDMHPFX, 20, 1024, 2, 1, 0x0, 0xf00, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_MDMPNG, 21, 1024, 0, 1, 0x1e, 0x0, 0, 0, 1, 1, 0),
SCT_ENTRY(LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0),
};
static int sdm845_qcom_llcc_probe(struct platform_device *pdev)
{
return qcom_llcc_probe(pdev, sdm845_data, ARRAY_SIZE(sdm845_data));
}
static const struct of_device_id sdm845_qcom_llcc_of_match[] = {
{ .compatible = "qcom,sdm845-llcc", },
{ }
};
static struct platform_driver sdm845_qcom_llcc_driver = {
.driver = {
.name = "sdm845-llcc",
.of_match_table = sdm845_qcom_llcc_of_match,
},
.probe = sdm845_qcom_llcc_probe,
};
module_platform_driver(sdm845_qcom_llcc_driver);
MODULE_DESCRIPTION("QCOM sdm845 LLCC driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
*
*/
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/soc/qcom/llcc-qcom.h>
#define ACTIVATE BIT(0)
#define DEACTIVATE BIT(1)
#define ACT_CTRL_OPCODE_ACTIVATE BIT(0)
#define ACT_CTRL_OPCODE_DEACTIVATE BIT(1)
#define ACT_CTRL_ACT_TRIG BIT(0)
#define ACT_CTRL_OPCODE_SHIFT 0x01
#define ATTR1_PROBE_TARGET_WAYS_SHIFT 0x02
#define ATTR1_FIXED_SIZE_SHIFT 0x03
#define ATTR1_PRIORITY_SHIFT 0x04
#define ATTR1_MAX_CAP_SHIFT 0x10
#define ATTR0_RES_WAYS_MASK GENMASK(11, 0)
#define ATTR0_BONUS_WAYS_MASK GENMASK(27, 16)
#define ATTR0_BONUS_WAYS_SHIFT 0x10
#define LLCC_STATUS_READ_DELAY 100
#define CACHE_LINE_SIZE_SHIFT 6
#define LLCC_COMMON_STATUS0 0x0003000c
#define LLCC_LB_CNT_MASK GENMASK(31, 28)
#define LLCC_LB_CNT_SHIFT 28
#define MAX_CAP_TO_BYTES(n) (n * SZ_1K)
#define LLCC_TRP_ACT_CTRLn(n) (n * SZ_4K)
#define LLCC_TRP_STATUSn(n) (4 + n * SZ_4K)
#define LLCC_TRP_ATTR0_CFGn(n) (0x21000 + SZ_8 * n)
#define LLCC_TRP_ATTR1_CFGn(n) (0x21004 + SZ_8 * n)
#define BANK_OFFSET_STRIDE 0x80000
static struct llcc_drv_data *drv_data;
static const struct regmap_config llcc_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.fast_io = true,
};
/**
* llcc_slice_getd - get llcc slice descriptor
* @uid: usecase_id for the client
*
* A pointer to llcc slice descriptor will be returned on success and
* and error pointer is returned on failure
*/
struct llcc_slice_desc *llcc_slice_getd(u32 uid)
{
const struct llcc_slice_config *cfg;
struct llcc_slice_desc *desc;
u32 sz, count;
cfg = drv_data->cfg;
sz = drv_data->cfg_size;
for (count = 0; cfg && count < sz; count++, cfg++)
if (cfg->usecase_id == uid)
break;
if (count == sz || !cfg)
return ERR_PTR(-ENODEV);
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (!desc)
return ERR_PTR(-ENOMEM);
desc->slice_id = cfg->slice_id;
desc->slice_size = cfg->max_cap;
return desc;
}
EXPORT_SYMBOL_GPL(llcc_slice_getd);
/**
* llcc_slice_putd - llcc slice descritpor
* @desc: Pointer to llcc slice descriptor
*/
void llcc_slice_putd(struct llcc_slice_desc *desc)
{
kfree(desc);
}
EXPORT_SYMBOL_GPL(llcc_slice_putd);
static int llcc_update_act_ctrl(u32 sid,
u32 act_ctrl_reg_val, u32 status)
{
u32 act_ctrl_reg;
u32 status_reg;
u32 slice_status;
int ret;
act_ctrl_reg = drv_data->bcast_off + LLCC_TRP_ACT_CTRLn(sid);
status_reg = drv_data->bcast_off + LLCC_TRP_STATUSn(sid);
/* Set the ACTIVE trigger */
act_ctrl_reg_val |= ACT_CTRL_ACT_TRIG;
ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val);
if (ret)
return ret;
/* Clear the ACTIVE trigger */
act_ctrl_reg_val &= ~ACT_CTRL_ACT_TRIG;
ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val);
if (ret)
return ret;
ret = regmap_read_poll_timeout(drv_data->regmap, status_reg,
slice_status, !(slice_status & status),
0, LLCC_STATUS_READ_DELAY);
return ret;
}
/**
* llcc_slice_activate - Activate the llcc slice
* @desc: Pointer to llcc slice descriptor
*
* A value of zero will be returned on success and a negative errno will
* be returned in error cases
*/
int llcc_slice_activate(struct llcc_slice_desc *desc)
{
int ret;
u32 act_ctrl_val;
mutex_lock(&drv_data->lock);
if (test_bit(desc->slice_id, drv_data->bitmap)) {
mutex_unlock(&drv_data->lock);
return 0;
}
act_ctrl_val = ACT_CTRL_OPCODE_ACTIVATE << ACT_CTRL_OPCODE_SHIFT;
ret = llcc_update_act_ctrl(desc->slice_id, act_ctrl_val,
DEACTIVATE);
if (ret) {
mutex_unlock(&drv_data->lock);
return ret;
}
__set_bit(desc->slice_id, drv_data->bitmap);
mutex_unlock(&drv_data->lock);
return ret;
}
EXPORT_SYMBOL_GPL(llcc_slice_activate);
/**
* llcc_slice_deactivate - Deactivate the llcc slice
* @desc: Pointer to llcc slice descriptor
*
* A value of zero will be returned on success and a negative errno will
* be returned in error cases
*/
int llcc_slice_deactivate(struct llcc_slice_desc *desc)
{
u32 act_ctrl_val;
int ret;
mutex_lock(&drv_data->lock);
if (!test_bit(desc->slice_id, drv_data->bitmap)) {
mutex_unlock(&drv_data->lock);
return 0;
}
act_ctrl_val = ACT_CTRL_OPCODE_DEACTIVATE << ACT_CTRL_OPCODE_SHIFT;
ret = llcc_update_act_ctrl(desc->slice_id, act_ctrl_val,
ACTIVATE);
if (ret) {
mutex_unlock(&drv_data->lock);
return ret;
}
__clear_bit(desc->slice_id, drv_data->bitmap);
mutex_unlock(&drv_data->lock);
return ret;
}
EXPORT_SYMBOL_GPL(llcc_slice_deactivate);
/**
* llcc_get_slice_id - return the slice id
* @desc: Pointer to llcc slice descriptor
*/
int llcc_get_slice_id(struct llcc_slice_desc *desc)
{
return desc->slice_id;
}
EXPORT_SYMBOL_GPL(llcc_get_slice_id);
/**
* llcc_get_slice_size - return the slice id
* @desc: Pointer to llcc slice descriptor
*/
size_t llcc_get_slice_size(struct llcc_slice_desc *desc)
{
return desc->slice_size;
}
EXPORT_SYMBOL_GPL(llcc_get_slice_size);
static int qcom_llcc_cfg_program(struct platform_device *pdev)
{
int i;
u32 attr1_cfg;
u32 attr0_cfg;
u32 attr1_val;
u32 attr0_val;
u32 max_cap_cacheline;
u32 sz;
int ret;
const struct llcc_slice_config *llcc_table;
struct llcc_slice_desc desc;
u32 bcast_off = drv_data->bcast_off;
sz = drv_data->cfg_size;
llcc_table = drv_data->cfg;
for (i = 0; i < sz; i++) {
attr1_cfg = bcast_off +
LLCC_TRP_ATTR1_CFGn(llcc_table[i].slice_id);
attr0_cfg = bcast_off +
LLCC_TRP_ATTR0_CFGn(llcc_table[i].slice_id);
attr1_val = llcc_table[i].cache_mode;
attr1_val |= llcc_table[i].probe_target_ways <<
ATTR1_PROBE_TARGET_WAYS_SHIFT;
attr1_val |= llcc_table[i].fixed_size <<
ATTR1_FIXED_SIZE_SHIFT;
attr1_val |= llcc_table[i].priority <<
ATTR1_PRIORITY_SHIFT;
max_cap_cacheline = MAX_CAP_TO_BYTES(llcc_table[i].max_cap);
/* LLCC instances can vary for each target.
* The SW writes to broadcast register which gets propagated
* to each llcc instace (llcc0,.. llccN).
* Since the size of the memory is divided equally amongst the
* llcc instances, we need to configure the max cap accordingly.
*/
max_cap_cacheline = max_cap_cacheline / drv_data->num_banks;
max_cap_cacheline >>= CACHE_LINE_SIZE_SHIFT;
attr1_val |= max_cap_cacheline << ATTR1_MAX_CAP_SHIFT;
attr0_val = llcc_table[i].res_ways & ATTR0_RES_WAYS_MASK;
attr0_val |= llcc_table[i].bonus_ways << ATTR0_BONUS_WAYS_SHIFT;
ret = regmap_write(drv_data->regmap, attr1_cfg, attr1_val);
if (ret)
return ret;
ret = regmap_write(drv_data->regmap, attr0_cfg, attr0_val);
if (ret)
return ret;
if (llcc_table[i].activate_on_init) {
desc.slice_id = llcc_table[i].slice_id;
ret = llcc_slice_activate(&desc);
}
}
return ret;
}
int qcom_llcc_probe(struct platform_device *pdev,
const struct llcc_slice_config *llcc_cfg, u32 sz)
{
u32 num_banks;
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *base;
int ret, i;
drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
drv_data->regmap = devm_regmap_init_mmio(dev, base,
&llcc_regmap_config);
if (IS_ERR(drv_data->regmap))
return PTR_ERR(drv_data->regmap);
ret = regmap_read(drv_data->regmap, LLCC_COMMON_STATUS0,
&num_banks);
if (ret)
return ret;
num_banks &= LLCC_LB_CNT_MASK;
num_banks >>= LLCC_LB_CNT_SHIFT;
drv_data->num_banks = num_banks;
for (i = 0; i < sz; i++)
if (llcc_cfg[i].slice_id > drv_data->max_slices)
drv_data->max_slices = llcc_cfg[i].slice_id;
drv_data->offsets = devm_kcalloc(dev, num_banks, sizeof(u32),
GFP_KERNEL);
if (!drv_data->offsets)
return -ENOMEM;
for (i = 0; i < num_banks; i++)
drv_data->offsets[i] = i * BANK_OFFSET_STRIDE;
drv_data->bcast_off = num_banks * BANK_OFFSET_STRIDE;
drv_data->bitmap = devm_kcalloc(dev,
BITS_TO_LONGS(drv_data->max_slices), sizeof(unsigned long),
GFP_KERNEL);
if (!drv_data->bitmap)
return -ENOMEM;
drv_data->cfg = llcc_cfg;
drv_data->cfg_size = sz;
mutex_init(&drv_data->lock);
platform_set_drvdata(pdev, drv_data);
return qcom_llcc_cfg_program(pdev);
}
EXPORT_SYMBOL_GPL(qcom_llcc_probe);
MODULE_LICENSE("GPL v2");

View File

@ -184,6 +184,7 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev)
device_initialize(&rmtfs_mem->dev);
rmtfs_mem->dev.parent = &pdev->dev;
rmtfs_mem->dev.groups = qcom_rmtfs_mem_groups;
rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device;
rmtfs_mem->base = devm_memremap(&rmtfs_mem->dev, rmtfs_mem->addr,
rmtfs_mem->size, MEMREMAP_WC);
@ -206,8 +207,6 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev)
goto put_device;
}
rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device;
ret = of_property_read_u32(node, "qcom,vmid", &vmid);
if (ret < 0 && ret != -EINVAL) {
dev_err(&pdev->dev, "failed to parse qcom,vmid\n");

View File

@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#ifndef __RPM_INTERNAL_H__
#define __RPM_INTERNAL_H__
#include <linux/bitmap.h>
#include <soc/qcom/tcs.h>
#define TCS_TYPE_NR 4
#define MAX_CMDS_PER_TCS 16
#define MAX_TCS_PER_TYPE 3
#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR)
#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE)
struct rsc_drv;
/**
* struct tcs_group: group of Trigger Command Sets (TCS) to send state requests
* to the controller
*
* @drv: the controller
* @type: type of the TCS in this group - active, sleep, wake
* @mask: mask of the TCSes relative to all the TCSes in the RSC
* @offset: start of the TCS group relative to the TCSes in the RSC
* @num_tcs: number of TCSes in this type
* @ncpt: number of commands in each TCS
* @lock: lock for synchronizing this TCS writes
* @req: requests that are sent from the TCS
* @cmd_cache: flattened cache of cmds in sleep/wake TCS
* @slots: indicates which of @cmd_addr are occupied
*/
struct tcs_group {
struct rsc_drv *drv;
int type;
u32 mask;
u32 offset;
int num_tcs;
int ncpt;
spinlock_t lock;
const struct tcs_request *req[MAX_TCS_PER_TYPE];
u32 *cmd_cache;
DECLARE_BITMAP(slots, MAX_TCS_SLOTS);
};
/**
* struct rpmh_request: the message to be sent to rpmh-rsc
*
* @msg: the request
* @cmd: the payload that will be part of the @msg
* @completion: triggered when request is done
* @dev: the device making the request
* @err: err return from the controller
* @needs_free: check to free dynamically allocated request object
*/
struct rpmh_request {
struct tcs_request msg;
struct tcs_cmd cmd[MAX_RPMH_PAYLOAD];
struct completion *completion;
const struct device *dev;
int err;
bool needs_free;
};
/**
* struct rpmh_ctrlr: our representation of the controller
*
* @cache: the list of cached requests
* @cache_lock: synchronize access to the cache data
* @dirty: was the cache updated since flush
* @batch_cache: Cache sleep and wake requests sent as batch
*/
struct rpmh_ctrlr {
struct list_head cache;
spinlock_t cache_lock;
bool dirty;
struct list_head batch_cache;
};
/**
* struct rsc_drv: the Direct Resource Voter (DRV) of the
* Resource State Coordinator controller (RSC)
*
* @name: controller identifier
* @tcs_base: start address of the TCS registers in this controller
* @id: instance id in the controller (Direct Resource Voter)
* @num_tcs: number of TCSes in this DRV
* @tcs: TCS groups
* @tcs_in_use: s/w state of the TCS
* @lock: synchronize state of the controller
* @client: handle to the DRV's client.
*/
struct rsc_drv {
const char *name;
void __iomem *tcs_base;
int id;
int num_tcs;
struct tcs_group tcs[TCS_TYPE_NR];
DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR);
spinlock_t lock;
struct rpmh_ctrlr client;
};
int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg);
int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv,
const struct tcs_request *msg);
int rpmh_rsc_invalidate(struct rsc_drv *drv);
void rpmh_tx_done(const struct tcs_request *msg, int r);
#endif /* __RPM_INTERNAL_H__ */

693
drivers/soc/qcom/rpmh-rsc.c Normal file
View File

@ -0,0 +1,693 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME
#include <linux/atomic.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <soc/qcom/cmd-db.h>
#include <soc/qcom/tcs.h>
#include <dt-bindings/soc/qcom,rpmh-rsc.h>
#include "rpmh-internal.h"
#define CREATE_TRACE_POINTS
#include "trace-rpmh.h"
#define RSC_DRV_TCS_OFFSET 672
#define RSC_DRV_CMD_OFFSET 20
/* DRV Configuration Information Register */
#define DRV_PRNT_CHLD_CONFIG 0x0C
#define DRV_NUM_TCS_MASK 0x3F
#define DRV_NUM_TCS_SHIFT 6
#define DRV_NCPT_MASK 0x1F
#define DRV_NCPT_SHIFT 27
/* Register offsets */
#define RSC_DRV_IRQ_ENABLE 0x00
#define RSC_DRV_IRQ_STATUS 0x04
#define RSC_DRV_IRQ_CLEAR 0x08
#define RSC_DRV_CMD_WAIT_FOR_CMPL 0x10
#define RSC_DRV_CONTROL 0x14
#define RSC_DRV_STATUS 0x18
#define RSC_DRV_CMD_ENABLE 0x1C
#define RSC_DRV_CMD_MSGID 0x30
#define RSC_DRV_CMD_ADDR 0x34
#define RSC_DRV_CMD_DATA 0x38
#define RSC_DRV_CMD_STATUS 0x3C
#define RSC_DRV_CMD_RESP_DATA 0x40
#define TCS_AMC_MODE_ENABLE BIT(16)
#define TCS_AMC_MODE_TRIGGER BIT(24)
/* TCS CMD register bit mask */
#define CMD_MSGID_LEN 8
#define CMD_MSGID_RESP_REQ BIT(8)
#define CMD_MSGID_WRITE BIT(16)
#define CMD_STATUS_ISSUED BIT(8)
#define CMD_STATUS_COMPL BIT(16)
static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
{
return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
RSC_DRV_CMD_OFFSET * cmd_id);
}
static void write_tcs_cmd(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id,
u32 data)
{
writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
RSC_DRV_CMD_OFFSET * cmd_id);
}
static void write_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, u32 data)
{
writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id);
}
static void write_tcs_reg_sync(struct rsc_drv *drv, int reg, int tcs_id,
u32 data)
{
writel(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id);
for (;;) {
if (data == readl(drv->tcs_base + reg +
RSC_DRV_TCS_OFFSET * tcs_id))
break;
udelay(1);
}
}
static bool tcs_is_free(struct rsc_drv *drv, int tcs_id)
{
return !test_bit(tcs_id, drv->tcs_in_use) &&
read_tcs_reg(drv, RSC_DRV_STATUS, tcs_id, 0);
}
static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type)
{
return &drv->tcs[type];
}
static int tcs_invalidate(struct rsc_drv *drv, int type)
{
int m;
struct tcs_group *tcs;
tcs = get_tcs_of_type(drv, type);
spin_lock(&tcs->lock);
if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS)) {
spin_unlock(&tcs->lock);
return 0;
}
for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) {
if (!tcs_is_free(drv, m)) {
spin_unlock(&tcs->lock);
return -EAGAIN;
}
write_tcs_reg_sync(drv, RSC_DRV_CMD_ENABLE, m, 0);
}
bitmap_zero(tcs->slots, MAX_TCS_SLOTS);
spin_unlock(&tcs->lock);
return 0;
}
/**
* rpmh_rsc_invalidate - Invalidate sleep and wake TCSes
*
* @drv: the RSC controller
*/
int rpmh_rsc_invalidate(struct rsc_drv *drv)
{
int ret;
ret = tcs_invalidate(drv, SLEEP_TCS);
if (!ret)
ret = tcs_invalidate(drv, WAKE_TCS);
return ret;
}
static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv,
const struct tcs_request *msg)
{
int type, ret;
struct tcs_group *tcs;
switch (msg->state) {
case RPMH_ACTIVE_ONLY_STATE:
type = ACTIVE_TCS;
break;
case RPMH_WAKE_ONLY_STATE:
type = WAKE_TCS;
break;
case RPMH_SLEEP_STATE:
type = SLEEP_TCS;
break;
default:
return ERR_PTR(-EINVAL);
}
/*
* If we are making an active request on a RSC that does not have a
* dedicated TCS for active state use, then re-purpose a wake TCS to
* send active votes.
* NOTE: The driver must be aware that this RSC does not have a
* dedicated AMC, and therefore would invalidate the sleep and wake
* TCSes before making an active state request.
*/
tcs = get_tcs_of_type(drv, type);
if (msg->state == RPMH_ACTIVE_ONLY_STATE && !tcs->num_tcs) {
tcs = get_tcs_of_type(drv, WAKE_TCS);
if (tcs->num_tcs) {
ret = rpmh_rsc_invalidate(drv);
if (ret)
return ERR_PTR(ret);
}
}
return tcs;
}
static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv,
int tcs_id)
{
struct tcs_group *tcs;
int i;
for (i = 0; i < TCS_TYPE_NR; i++) {
tcs = &drv->tcs[i];
if (tcs->mask & BIT(tcs_id))
return tcs->req[tcs_id - tcs->offset];
}
return NULL;
}
/**
* tcs_tx_done: TX Done interrupt handler
*/
static irqreturn_t tcs_tx_done(int irq, void *p)
{
struct rsc_drv *drv = p;
int i, j, err = 0;
unsigned long irq_status;
const struct tcs_request *req;
struct tcs_cmd *cmd;
irq_status = read_tcs_reg(drv, RSC_DRV_IRQ_STATUS, 0, 0);
for_each_set_bit(i, &irq_status, BITS_PER_LONG) {
req = get_req_from_tcs(drv, i);
if (!req) {
WARN_ON(1);
goto skip;
}
err = 0;
for (j = 0; j < req->num_cmds; j++) {
u32 sts;
cmd = &req->cmds[j];
sts = read_tcs_reg(drv, RSC_DRV_CMD_STATUS, i, j);
if (!(sts & CMD_STATUS_ISSUED) ||
((req->wait_for_compl || cmd->wait) &&
!(sts & CMD_STATUS_COMPL))) {
pr_err("Incomplete request: %s: addr=%#x data=%#x",
drv->name, cmd->addr, cmd->data);
err = -EIO;
}
}
trace_rpmh_tx_done(drv, i, req, err);
skip:
/* Reclaim the TCS */
write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0);
write_tcs_reg(drv, RSC_DRV_IRQ_CLEAR, 0, BIT(i));
spin_lock(&drv->lock);
clear_bit(i, drv->tcs_in_use);
spin_unlock(&drv->lock);
if (req)
rpmh_tx_done(req, err);
}
return IRQ_HANDLED;
}
static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id,
const struct tcs_request *msg)
{
u32 msgid, cmd_msgid;
u32 cmd_enable = 0;
u32 cmd_complete;
struct tcs_cmd *cmd;
int i, j;
cmd_msgid = CMD_MSGID_LEN;
cmd_msgid |= msg->wait_for_compl ? CMD_MSGID_RESP_REQ : 0;
cmd_msgid |= CMD_MSGID_WRITE;
cmd_complete = read_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, 0);
for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) {
cmd = &msg->cmds[i];
cmd_enable |= BIT(j);
cmd_complete |= cmd->wait << j;
msgid = cmd_msgid;
msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0;
write_tcs_cmd(drv, RSC_DRV_CMD_MSGID, tcs_id, j, msgid);
write_tcs_cmd(drv, RSC_DRV_CMD_ADDR, tcs_id, j, cmd->addr);
write_tcs_cmd(drv, RSC_DRV_CMD_DATA, tcs_id, j, cmd->data);
trace_rpmh_send_msg(drv, tcs_id, j, msgid, cmd);
}
write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, cmd_complete);
cmd_enable |= read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0);
write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, cmd_enable);
}
static void __tcs_trigger(struct rsc_drv *drv, int tcs_id)
{
u32 enable;
/*
* HW req: Clear the DRV_CONTROL and enable TCS again
* While clearing ensure that the AMC mode trigger is cleared
* and then the mode enable is cleared.
*/
enable = read_tcs_reg(drv, RSC_DRV_CONTROL, tcs_id, 0);
enable &= ~TCS_AMC_MODE_TRIGGER;
write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable);
enable &= ~TCS_AMC_MODE_ENABLE;
write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable);
/* Enable the AMC mode on the TCS and then trigger the TCS */
enable = TCS_AMC_MODE_ENABLE;
write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable);
enable |= TCS_AMC_MODE_TRIGGER;
write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable);
}
static int check_for_req_inflight(struct rsc_drv *drv, struct tcs_group *tcs,
const struct tcs_request *msg)
{
unsigned long curr_enabled;
u32 addr;
int i, j, k;
int tcs_id = tcs->offset;
for (i = 0; i < tcs->num_tcs; i++, tcs_id++) {
if (tcs_is_free(drv, tcs_id))
continue;
curr_enabled = read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0);
for_each_set_bit(j, &curr_enabled, MAX_CMDS_PER_TCS) {
addr = read_tcs_reg(drv, RSC_DRV_CMD_ADDR, tcs_id, j);
for (k = 0; k < msg->num_cmds; k++) {
if (addr == msg->cmds[k].addr)
return -EBUSY;
}
}
}
return 0;
}
static int find_free_tcs(struct tcs_group *tcs)
{
int i;
for (i = 0; i < tcs->num_tcs; i++) {
if (tcs_is_free(tcs->drv, tcs->offset + i))
return tcs->offset + i;
}
return -EBUSY;
}
static int tcs_write(struct rsc_drv *drv, const struct tcs_request *msg)
{
struct tcs_group *tcs;
int tcs_id;
unsigned long flags;
int ret;
tcs = get_tcs_for_msg(drv, msg);
if (IS_ERR(tcs))
return PTR_ERR(tcs);
spin_lock_irqsave(&tcs->lock, flags);
spin_lock(&drv->lock);
/*
* The h/w does not like if we send a request to the same address,
* when one is already in-flight or being processed.
*/
ret = check_for_req_inflight(drv, tcs, msg);
if (ret) {
spin_unlock(&drv->lock);
goto done_write;
}
tcs_id = find_free_tcs(tcs);
if (tcs_id < 0) {
ret = tcs_id;
spin_unlock(&drv->lock);
goto done_write;
}
tcs->req[tcs_id - tcs->offset] = msg;
set_bit(tcs_id, drv->tcs_in_use);
spin_unlock(&drv->lock);
__tcs_buffer_write(drv, tcs_id, 0, msg);
__tcs_trigger(drv, tcs_id);
done_write:
spin_unlock_irqrestore(&tcs->lock, flags);
return ret;
}
/**
* rpmh_rsc_send_data: Validate the incoming message and write to the
* appropriate TCS block.
*
* @drv: the controller
* @msg: the data to be sent
*
* Return: 0 on success, -EINVAL on error.
* Note: This call blocks until a valid data is written to the TCS.
*/
int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg)
{
int ret;
if (!msg || !msg->cmds || !msg->num_cmds ||
msg->num_cmds > MAX_RPMH_PAYLOAD) {
WARN_ON(1);
return -EINVAL;
}
do {
ret = tcs_write(drv, msg);
if (ret == -EBUSY) {
pr_info_ratelimited("TCS Busy, retrying RPMH message send: addr=%#x\n",
msg->cmds[0].addr);
udelay(10);
}
} while (ret == -EBUSY);
return ret;
}
static int find_match(const struct tcs_group *tcs, const struct tcs_cmd *cmd,
int len)
{
int i, j;
/* Check for already cached commands */
for_each_set_bit(i, tcs->slots, MAX_TCS_SLOTS) {
if (tcs->cmd_cache[i] != cmd[0].addr)
continue;
if (i + len >= tcs->num_tcs * tcs->ncpt)
goto seq_err;
for (j = 0; j < len; j++) {
if (tcs->cmd_cache[i + j] != cmd[j].addr)
goto seq_err;
}
return i;
}
return -ENODATA;
seq_err:
WARN(1, "Message does not match previous sequence.\n");
return -EINVAL;
}
static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg,
int *tcs_id, int *cmd_id)
{
int slot, offset;
int i = 0;
/* Find if we already have the msg in our TCS */
slot = find_match(tcs, msg->cmds, msg->num_cmds);
if (slot >= 0)
goto copy_data;
/* Do over, until we can fit the full payload in a TCS */
do {
slot = bitmap_find_next_zero_area(tcs->slots, MAX_TCS_SLOTS,
i, msg->num_cmds, 0);
if (slot == tcs->num_tcs * tcs->ncpt)
return -ENOMEM;
i += tcs->ncpt;
} while (slot + msg->num_cmds - 1 >= i);
copy_data:
bitmap_set(tcs->slots, slot, msg->num_cmds);
/* Copy the addresses of the resources over to the slots */
for (i = 0; i < msg->num_cmds; i++)
tcs->cmd_cache[slot + i] = msg->cmds[i].addr;
offset = slot / tcs->ncpt;
*tcs_id = offset + tcs->offset;
*cmd_id = slot % tcs->ncpt;
return 0;
}
static int tcs_ctrl_write(struct rsc_drv *drv, const struct tcs_request *msg)
{
struct tcs_group *tcs;
int tcs_id = 0, cmd_id = 0;
unsigned long flags;
int ret;
tcs = get_tcs_for_msg(drv, msg);
if (IS_ERR(tcs))
return PTR_ERR(tcs);
spin_lock_irqsave(&tcs->lock, flags);
/* find the TCS id and the command in the TCS to write to */
ret = find_slots(tcs, msg, &tcs_id, &cmd_id);
if (!ret)
__tcs_buffer_write(drv, tcs_id, cmd_id, msg);
spin_unlock_irqrestore(&tcs->lock, flags);
return ret;
}
/**
* rpmh_rsc_write_ctrl_data: Write request to the controller
*
* @drv: the controller
* @msg: the data to be written to the controller
*
* There is no response returned for writing the request to the controller.
*/
int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg)
{
if (!msg || !msg->cmds || !msg->num_cmds ||
msg->num_cmds > MAX_RPMH_PAYLOAD) {
pr_err("Payload error\n");
return -EINVAL;
}
/* Data sent to this API will not be sent immediately */
if (msg->state == RPMH_ACTIVE_ONLY_STATE)
return -EINVAL;
return tcs_ctrl_write(drv, msg);
}
static int rpmh_probe_tcs_config(struct platform_device *pdev,
struct rsc_drv *drv)
{
struct tcs_type_config {
u32 type;
u32 n;
} tcs_cfg[TCS_TYPE_NR] = { { 0 } };
struct device_node *dn = pdev->dev.of_node;
u32 config, max_tcs, ncpt, offset;
int i, ret, n, st = 0;
struct tcs_group *tcs;
struct resource *res;
void __iomem *base;
char drv_id[10] = {0};
snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, drv_id);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
ret = of_property_read_u32(dn, "qcom,tcs-offset", &offset);
if (ret)
return ret;
drv->tcs_base = base + offset;
config = readl_relaxed(base + DRV_PRNT_CHLD_CONFIG);
max_tcs = config;
max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id);
max_tcs = max_tcs >> (DRV_NUM_TCS_SHIFT * drv->id);
ncpt = config & (DRV_NCPT_MASK << DRV_NCPT_SHIFT);
ncpt = ncpt >> DRV_NCPT_SHIFT;
n = of_property_count_u32_elems(dn, "qcom,tcs-config");
if (n != 2 * TCS_TYPE_NR)
return -EINVAL;
for (i = 0; i < TCS_TYPE_NR; i++) {
ret = of_property_read_u32_index(dn, "qcom,tcs-config",
i * 2, &tcs_cfg[i].type);
if (ret)
return ret;
if (tcs_cfg[i].type >= TCS_TYPE_NR)
return -EINVAL;
ret = of_property_read_u32_index(dn, "qcom,tcs-config",
i * 2 + 1, &tcs_cfg[i].n);
if (ret)
return ret;
if (tcs_cfg[i].n > MAX_TCS_PER_TYPE)
return -EINVAL;
}
for (i = 0; i < TCS_TYPE_NR; i++) {
tcs = &drv->tcs[tcs_cfg[i].type];
if (tcs->drv)
return -EINVAL;
tcs->drv = drv;
tcs->type = tcs_cfg[i].type;
tcs->num_tcs = tcs_cfg[i].n;
tcs->ncpt = ncpt;
spin_lock_init(&tcs->lock);
if (!tcs->num_tcs || tcs->type == CONTROL_TCS)
continue;
if (st + tcs->num_tcs > max_tcs ||
st + tcs->num_tcs >= BITS_PER_BYTE * sizeof(tcs->mask))
return -EINVAL;
tcs->mask = ((1 << tcs->num_tcs) - 1) << st;
tcs->offset = st;
st += tcs->num_tcs;
/*
* Allocate memory to cache sleep and wake requests to
* avoid reading TCS register memory.
*/
if (tcs->type == ACTIVE_TCS)
continue;
tcs->cmd_cache = devm_kcalloc(&pdev->dev,
tcs->num_tcs * ncpt, sizeof(u32),
GFP_KERNEL);
if (!tcs->cmd_cache)
return -ENOMEM;
}
drv->num_tcs = st;
return 0;
}
static int rpmh_rsc_probe(struct platform_device *pdev)
{
struct device_node *dn = pdev->dev.of_node;
struct rsc_drv *drv;
int ret, irq;
/*
* Even though RPMh doesn't directly use cmd-db, all of its children
* do. To avoid adding this check to our children we'll do it now.
*/
ret = cmd_db_ready();
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Command DB not available (%d)\n",
ret);
return ret;
}
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
ret = of_property_read_u32(dn, "qcom,drv-id", &drv->id);
if (ret)
return ret;
drv->name = of_get_property(dn, "label", NULL);
if (!drv->name)
drv->name = dev_name(&pdev->dev);
ret = rpmh_probe_tcs_config(pdev, drv);
if (ret)
return ret;
spin_lock_init(&drv->lock);
bitmap_zero(drv->tcs_in_use, MAX_TCS_NR);
irq = platform_get_irq(pdev, drv->id);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, tcs_tx_done,
IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
drv->name, drv);
if (ret)
return ret;
/* Enable the active TCS to send requests immediately */
write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask);
spin_lock_init(&drv->client.cache_lock);
INIT_LIST_HEAD(&drv->client.cache);
INIT_LIST_HEAD(&drv->client.batch_cache);
dev_set_drvdata(&pdev->dev, drv);
return devm_of_platform_populate(&pdev->dev);
}
static const struct of_device_id rpmh_drv_match[] = {
{ .compatible = "qcom,rpmh-rsc", },
{ }
};
static struct platform_driver rpmh_driver = {
.probe = rpmh_rsc_probe,
.driver = {
.name = "rpmh",
.of_match_table = rpmh_drv_match,
},
};
static int __init rpmh_driver_init(void)
{
return platform_driver_register(&rpmh_driver);
}
arch_initcall(rpmh_driver_init);

513
drivers/soc/qcom/rpmh.c Normal file
View File

@ -0,0 +1,513 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#include <linux/atomic.h>
#include <linux/bug.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <soc/qcom/rpmh.h>
#include "rpmh-internal.h"
#define RPMH_TIMEOUT_MS msecs_to_jiffies(10000)
#define DEFINE_RPMH_MSG_ONSTACK(dev, s, q, name) \
struct rpmh_request name = { \
.msg = { \
.state = s, \
.cmds = name.cmd, \
.num_cmds = 0, \
.wait_for_compl = true, \
}, \
.cmd = { { 0 } }, \
.completion = q, \
.dev = dev, \
.needs_free = false, \
}
#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client)
/**
* struct cache_req: the request object for caching
*
* @addr: the address of the resource
* @sleep_val: the sleep vote
* @wake_val: the wake vote
* @list: linked list obj
*/
struct cache_req {
u32 addr;
u32 sleep_val;
u32 wake_val;
struct list_head list;
};
/**
* struct batch_cache_req - An entry in our batch catch
*
* @list: linked list obj
* @count: number of messages
* @rpm_msgs: the messages
*/
struct batch_cache_req {
struct list_head list;
int count;
struct rpmh_request rpm_msgs[];
};
static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev)
{
struct rsc_drv *drv = dev_get_drvdata(dev->parent);
return &drv->client;
}
void rpmh_tx_done(const struct tcs_request *msg, int r)
{
struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request,
msg);
struct completion *compl = rpm_msg->completion;
rpm_msg->err = r;
if (r)
dev_err(rpm_msg->dev, "RPMH TX fail in msg addr=%#x, err=%d\n",
rpm_msg->msg.cmds[0].addr, r);
if (!compl)
goto exit;
/* Signal the blocking thread we are done */
complete(compl);
exit:
if (rpm_msg->needs_free)
kfree(rpm_msg);
}
static struct cache_req *__find_req(struct rpmh_ctrlr *ctrlr, u32 addr)
{
struct cache_req *p, *req = NULL;
list_for_each_entry(p, &ctrlr->cache, list) {
if (p->addr == addr) {
req = p;
break;
}
}
return req;
}
static struct cache_req *cache_rpm_request(struct rpmh_ctrlr *ctrlr,
enum rpmh_state state,
struct tcs_cmd *cmd)
{
struct cache_req *req;
unsigned long flags;
spin_lock_irqsave(&ctrlr->cache_lock, flags);
req = __find_req(ctrlr, cmd->addr);
if (req)
goto existing;
req = kzalloc(sizeof(*req), GFP_ATOMIC);
if (!req) {
req = ERR_PTR(-ENOMEM);
goto unlock;
}
req->addr = cmd->addr;
req->sleep_val = req->wake_val = UINT_MAX;
INIT_LIST_HEAD(&req->list);
list_add_tail(&req->list, &ctrlr->cache);
existing:
switch (state) {
case RPMH_ACTIVE_ONLY_STATE:
if (req->sleep_val != UINT_MAX)
req->wake_val = cmd->data;
break;
case RPMH_WAKE_ONLY_STATE:
req->wake_val = cmd->data;
break;
case RPMH_SLEEP_STATE:
req->sleep_val = cmd->data;
break;
default:
break;
}
ctrlr->dirty = true;
unlock:
spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
return req;
}
/**
* __rpmh_write: Cache and send the RPMH request
*
* @dev: The device making the request
* @state: Active/Sleep request type
* @rpm_msg: The data that needs to be sent (cmds).
*
* Cache the RPMH request and send if the state is ACTIVE_ONLY.
* SLEEP/WAKE_ONLY requests are not sent to the controller at
* this time. Use rpmh_flush() to send them to the controller.
*/
static int __rpmh_write(const struct device *dev, enum rpmh_state state,
struct rpmh_request *rpm_msg)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
int ret = -EINVAL;
struct cache_req *req;
int i;
rpm_msg->msg.state = state;
/* Cache the request in our store and link the payload */
for (i = 0; i < rpm_msg->msg.num_cmds; i++) {
req = cache_rpm_request(ctrlr, state, &rpm_msg->msg.cmds[i]);
if (IS_ERR(req))
return PTR_ERR(req);
}
rpm_msg->msg.state = state;
if (state == RPMH_ACTIVE_ONLY_STATE) {
WARN_ON(irqs_disabled());
ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg);
} else {
ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr),
&rpm_msg->msg);
/* Clean up our call by spoofing tx_done */
rpmh_tx_done(&rpm_msg->msg, ret);
}
return ret;
}
static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n)
{
if (!cmd || !n || n > MAX_RPMH_PAYLOAD)
return -EINVAL;
memcpy(req->cmd, cmd, n * sizeof(*cmd));
req->msg.state = state;
req->msg.cmds = req->cmd;
req->msg.num_cmds = n;
return 0;
}
/**
* rpmh_write_async: Write a set of RPMH commands
*
* @dev: The device making the request
* @state: Active/sleep set
* @cmd: The payload data
* @n: The number of elements in payload
*
* Write a set of RPMH commands, the order of commands is maintained
* and will be sent as a single shot.
*/
int rpmh_write_async(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n)
{
struct rpmh_request *rpm_msg;
int ret;
rpm_msg = kzalloc(sizeof(*rpm_msg), GFP_ATOMIC);
if (!rpm_msg)
return -ENOMEM;
rpm_msg->needs_free = true;
ret = __fill_rpmh_msg(rpm_msg, state, cmd, n);
if (ret) {
kfree(rpm_msg);
return ret;
}
return __rpmh_write(dev, state, rpm_msg);
}
EXPORT_SYMBOL(rpmh_write_async);
/**
* rpmh_write: Write a set of RPMH commands and block until response
*
* @rc: The RPMH handle got from rpmh_get_client
* @state: Active/sleep set
* @cmd: The payload data
* @n: The number of elements in @cmd
*
* May sleep. Do not call from atomic contexts.
*/
int rpmh_write(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n)
{
DECLARE_COMPLETION_ONSTACK(compl);
DEFINE_RPMH_MSG_ONSTACK(dev, state, &compl, rpm_msg);
int ret;
if (!cmd || !n || n > MAX_RPMH_PAYLOAD)
return -EINVAL;
memcpy(rpm_msg.cmd, cmd, n * sizeof(*cmd));
rpm_msg.msg.num_cmds = n;
ret = __rpmh_write(dev, state, &rpm_msg);
if (ret)
return ret;
ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS);
WARN_ON(!ret);
return (ret > 0) ? 0 : -ETIMEDOUT;
}
EXPORT_SYMBOL(rpmh_write);
static void cache_batch(struct rpmh_ctrlr *ctrlr, struct batch_cache_req *req)
{
unsigned long flags;
spin_lock_irqsave(&ctrlr->cache_lock, flags);
list_add_tail(&req->list, &ctrlr->batch_cache);
spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
}
static int flush_batch(struct rpmh_ctrlr *ctrlr)
{
struct batch_cache_req *req;
const struct rpmh_request *rpm_msg;
unsigned long flags;
int ret = 0;
int i;
/* Send Sleep/Wake requests to the controller, expect no response */
spin_lock_irqsave(&ctrlr->cache_lock, flags);
list_for_each_entry(req, &ctrlr->batch_cache, list) {
for (i = 0; i < req->count; i++) {
rpm_msg = req->rpm_msgs + i;
ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr),
&rpm_msg->msg);
if (ret)
break;
}
}
spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
return ret;
}
static void invalidate_batch(struct rpmh_ctrlr *ctrlr)
{
struct batch_cache_req *req, *tmp;
unsigned long flags;
spin_lock_irqsave(&ctrlr->cache_lock, flags);
list_for_each_entry_safe(req, tmp, &ctrlr->batch_cache, list)
kfree(req);
INIT_LIST_HEAD(&ctrlr->batch_cache);
spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
}
/**
* rpmh_write_batch: Write multiple sets of RPMH commands and wait for the
* batch to finish.
*
* @dev: the device making the request
* @state: Active/sleep set
* @cmd: The payload data
* @n: The array of count of elements in each batch, 0 terminated.
*
* Write a request to the RSC controller without caching. If the request
* state is ACTIVE, then the requests are treated as completion request
* and sent to the controller immediately. The function waits until all the
* commands are complete. If the request was to SLEEP or WAKE_ONLY, then the
* request is sent as fire-n-forget and no ack is expected.
*
* May sleep. Do not call from atomic contexts for ACTIVE_ONLY requests.
*/
int rpmh_write_batch(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 *n)
{
struct batch_cache_req *req;
struct rpmh_request *rpm_msgs;
DECLARE_COMPLETION_ONSTACK(compl);
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
unsigned long time_left;
int count = 0;
int ret, i, j;
if (!cmd || !n)
return -EINVAL;
while (n[count] > 0)
count++;
if (!count)
return -EINVAL;
req = kzalloc(sizeof(*req) + count * sizeof(req->rpm_msgs[0]),
GFP_ATOMIC);
if (!req)
return -ENOMEM;
req->count = count;
rpm_msgs = req->rpm_msgs;
for (i = 0; i < count; i++) {
__fill_rpmh_msg(rpm_msgs + i, state, cmd, n[i]);
cmd += n[i];
}
if (state != RPMH_ACTIVE_ONLY_STATE) {
cache_batch(ctrlr, req);
return 0;
}
for (i = 0; i < count; i++) {
rpm_msgs[i].completion = &compl;
ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msgs[i].msg);
if (ret) {
pr_err("Error(%d) sending RPMH message addr=%#x\n",
ret, rpm_msgs[i].msg.cmds[0].addr);
for (j = i; j < count; j++)
rpmh_tx_done(&rpm_msgs[j].msg, ret);
break;
}
}
time_left = RPMH_TIMEOUT_MS;
for (i = 0; i < count; i++) {
time_left = wait_for_completion_timeout(&compl, time_left);
if (!time_left) {
/*
* Better hope they never finish because they'll signal
* the completion on our stack and that's bad once
* we've returned from the function.
*/
WARN_ON(1);
ret = -ETIMEDOUT;
goto exit;
}
}
exit:
kfree(req);
return ret;
}
EXPORT_SYMBOL(rpmh_write_batch);
static int is_req_valid(struct cache_req *req)
{
return (req->sleep_val != UINT_MAX &&
req->wake_val != UINT_MAX &&
req->sleep_val != req->wake_val);
}
static int send_single(const struct device *dev, enum rpmh_state state,
u32 addr, u32 data)
{
DEFINE_RPMH_MSG_ONSTACK(dev, state, NULL, rpm_msg);
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
/* Wake sets are always complete and sleep sets are not */
rpm_msg.msg.wait_for_compl = (state == RPMH_WAKE_ONLY_STATE);
rpm_msg.cmd[0].addr = addr;
rpm_msg.cmd[0].data = data;
rpm_msg.msg.num_cmds = 1;
return rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), &rpm_msg.msg);
}
/**
* rpmh_flush: Flushes the buffered active and sleep sets to TCS
*
* @dev: The device making the request
*
* Return: -EBUSY if the controller is busy, probably waiting on a response
* to a RPMH request sent earlier.
*
* This function is always called from the sleep code from the last CPU
* that is powering down the entire system. Since no other RPMH API would be
* executing at this time, it is safe to run lockless.
*/
int rpmh_flush(const struct device *dev)
{
struct cache_req *p;
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
int ret;
if (!ctrlr->dirty) {
pr_debug("Skipping flush, TCS has latest data.\n");
return 0;
}
/* First flush the cached batch requests */
ret = flush_batch(ctrlr);
if (ret)
return ret;
/*
* Nobody else should be calling this function other than system PM,
* hence we can run without locks.
*/
list_for_each_entry(p, &ctrlr->cache, list) {
if (!is_req_valid(p)) {
pr_debug("%s: skipping RPMH req: a:%#x s:%#x w:%#x",
__func__, p->addr, p->sleep_val, p->wake_val);
continue;
}
ret = send_single(dev, RPMH_SLEEP_STATE, p->addr, p->sleep_val);
if (ret)
return ret;
ret = send_single(dev, RPMH_WAKE_ONLY_STATE,
p->addr, p->wake_val);
if (ret)
return ret;
}
ctrlr->dirty = false;
return 0;
}
EXPORT_SYMBOL(rpmh_flush);
/**
* rpmh_invalidate: Invalidate all sleep and active sets
* sets.
*
* @dev: The device making the request
*
* Invalidate the sleep and active values in the TCS blocks.
*/
int rpmh_invalidate(const struct device *dev)
{
struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
int ret;
invalidate_batch(ctrlr);
ctrlr->dirty = true;
do {
ret = rpmh_rsc_invalidate(ctrlr_to_drv(ctrlr));
} while (ret == -EAGAIN);
return ret;
}
EXPORT_SYMBOL(rpmh_invalidate);

View File

@ -364,11 +364,6 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem,
end = phdr_to_last_uncached_entry(phdr);
cached = phdr_to_last_cached_entry(phdr);
if (smem->global_partition) {
dev_err(smem->dev, "Already found the global partition\n");
return -EINVAL;
}
while (hdr < end) {
if (hdr->canary != SMEM_PRIVATE_CANARY)
goto bad_canary;
@ -736,6 +731,11 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem)
bool found = false;
int i;
if (smem->global_partition) {
dev_err(smem->dev, "Already found the global partition\n");
return -EINVAL;
}
ptable = qcom_smem_get_ptable(smem);
if (IS_ERR(ptable))
return PTR_ERR(ptable);

View File

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#if !defined(_TRACE_RPMH_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_RPMH_H
#undef TRACE_SYSTEM
#define TRACE_SYSTEM rpmh
#include <linux/tracepoint.h>
#include "rpmh-internal.h"
TRACE_EVENT(rpmh_tx_done,
TP_PROTO(struct rsc_drv *d, int m, const struct tcs_request *r, int e),
TP_ARGS(d, m, r, e),
TP_STRUCT__entry(
__string(name, d->name)
__field(int, m)
__field(u32, addr)
__field(u32, data)
__field(int, err)
),
TP_fast_assign(
__assign_str(name, d->name);
__entry->m = m;
__entry->addr = r->cmds[0].addr;
__entry->data = r->cmds[0].data;
__entry->err = e;
),
TP_printk("%s: ack: tcs-m: %d addr: %#x data: %#x errno: %d",
__get_str(name), __entry->m, __entry->addr, __entry->data,
__entry->err)
);
TRACE_EVENT(rpmh_send_msg,
TP_PROTO(struct rsc_drv *d, int m, int n, u32 h,
const struct tcs_cmd *c),
TP_ARGS(d, m, n, h, c),
TP_STRUCT__entry(
__string(name, d->name)
__field(int, m)
__field(int, n)
__field(u32, hdr)
__field(u32, addr)
__field(u32, data)
__field(bool, wait)
),
TP_fast_assign(
__assign_str(name, d->name);
__entry->m = m;
__entry->n = n;
__entry->hdr = h;
__entry->addr = c->addr;
__entry->data = c->data;
__entry->wait = c->wait;
),
TP_printk("%s: send-msg: tcs(m): %d cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d",
__get_str(name), __entry->m, __entry->n, __entry->hdr,
__entry->addr, __entry->data, __entry->wait)
);
#endif /* _TRACE_RPMH_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace-rpmh
#include <trace/define_trace.h>

View File

@ -1,10 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2014 Google, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Device Tree binding constants for the Maxim 77802 PMIC regulators
*/

View File

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */
#ifndef __QCOM_RPMH_REGULATOR_H
#define __QCOM_RPMH_REGULATOR_H
/*
* These mode constants may be used to specify modes for various RPMh regulator
* device tree properties (e.g. regulator-initial-mode). Each type of regulator
* supports a subset of the possible modes.
*
* %RPMH_REGULATOR_MODE_RET: Retention mode in which only an extremely small
* load current is allowed. This mode is supported
* by LDO and SMPS type regulators.
* %RPMH_REGULATOR_MODE_LPM: Low power mode in which a small load current is
* allowed. This mode corresponds to PFM for SMPS
* and BOB type regulators. This mode is supported
* by LDO, HFSMPS, BOB, and PMIC4 FTSMPS type
* regulators.
* %RPMH_REGULATOR_MODE_AUTO: Auto mode in which the regulator hardware
* automatically switches between LPM and HPM based
* upon the real-time load current. This mode is
* supported by HFSMPS, BOB, and PMIC4 FTSMPS type
* regulators.
* %RPMH_REGULATOR_MODE_HPM: High power mode in which the full rated current
* of the regulator is allowed. This mode
* corresponds to PWM for SMPS and BOB type
* regulators. This mode is supported by all types
* of regulators.
*/
#define RPMH_REGULATOR_MODE_RET 0
#define RPMH_REGULATOR_MODE_LPM 1
#define RPMH_REGULATOR_MODE_AUTO 2
#define RPMH_REGULATOR_MODE_HPM 3
#endif

View File

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#ifndef __DT_QCOM_RPMH_RSC_H__
#define __DT_QCOM_RPMH_RSC_H__
#define SLEEP_TCS 0
#define WAKE_TCS 1
#define ACTIVE_TCS 2
#define CONTROL_TCS 3
#endif /* __DT_QCOM_RPMH_RSC_H__ */

View File

@ -1316,6 +1316,7 @@ extern const char *dev_driver_string(const struct device *dev);
struct device_link *device_link_add(struct device *consumer,
struct device *supplier, u32 flags);
void device_link_del(struct device_link *link);
void device_link_remove(void *consumer, struct device *supplier);
#ifdef CONFIG_PRINTK

View File

@ -87,6 +87,10 @@ static inline int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr,
static inline int
qcom_scm_pas_auth_and_reset(u32 peripheral) { return -ENODEV; }
static inline int qcom_scm_pas_shutdown(u32 peripheral) { return -ENODEV; }
static inline int qcom_scm_assign_mem(phys_addr_t mem_addr, size_t mem_sz,
unsigned int *src,
struct qcom_scm_vmperm *newvm,
int dest_cnt) { return -ENODEV; }
static inline void qcom_scm_cpu_power_down(u32 flags) {}
static inline u32 qcom_scm_get_version(void) { return 0; }
static inline u32

View File

@ -46,7 +46,7 @@ enum regulator_status {
/**
* struct regulator_linear_range - specify linear voltage ranges
*
* Specify a range of voltages for regulator_map_linar_range() and
* Specify a range of voltages for regulator_map_linear_range() and
* regulator_list_linear_range().
*
* @min_uV: Lowest voltage in range
@ -220,7 +220,7 @@ struct regulator_ops {
/* set regulator suspend operating mode (defined in consumer.h) */
int (*set_suspend_mode) (struct regulator_dev *, unsigned int mode);
int (*resume_early)(struct regulator_dev *rdev);
int (*resume)(struct regulator_dev *rdev);
int (*set_pull_down) (struct regulator_dev *);
};

View File

@ -64,6 +64,17 @@
#define PFUZE3000_VLDO3 11
#define PFUZE3000_VLDO4 12
#define PFUZE3001_SW1 0
#define PFUZE3001_SW2 1
#define PFUZE3001_SW3 2
#define PFUZE3001_VSNVS 3
#define PFUZE3001_VLDO1 4
#define PFUZE3001_VLDO2 5
#define PFUZE3001_VCCSD 6
#define PFUZE3001_V33 7
#define PFUZE3001_VLDO3 8
#define PFUZE3001_VLDO4 9
struct regulator_init_data;
struct pfuze_regulator_platform_data {

View File

@ -0,0 +1,180 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
*
*/
#include <linux/platform_device.h>
#ifndef __LLCC_QCOM__
#define __LLCC_QCOM__
#define LLCC_CPUSS 1
#define LLCC_VIDSC0 2
#define LLCC_VIDSC1 3
#define LLCC_ROTATOR 4
#define LLCC_VOICE 5
#define LLCC_AUDIO 6
#define LLCC_MDMHPGRW 7
#define LLCC_MDM 8
#define LLCC_CMPT 10
#define LLCC_GPUHTW 11
#define LLCC_GPU 12
#define LLCC_MMUHWT 13
#define LLCC_CMPTDMA 15
#define LLCC_DISP 16
#define LLCC_VIDFW 17
#define LLCC_MDMHPFX 20
#define LLCC_MDMPNG 21
#define LLCC_AUDHW 22
/**
* llcc_slice_desc - Cache slice descriptor
* @slice_id: llcc slice id
* @slice_size: Size allocated for the llcc slice
*/
struct llcc_slice_desc {
u32 slice_id;
size_t slice_size;
};
/**
* llcc_slice_config - Data associated with the llcc slice
* @usecase_id: usecase id for which the llcc slice is used
* @slice_id: llcc slice id assigned to each slice
* @max_cap: maximum capacity of the llcc slice
* @priority: priority of the llcc slice
* @fixed_size: whether the llcc slice can grow beyond its size
* @bonus_ways: bonus ways associated with llcc slice
* @res_ways: reserved ways associated with llcc slice
* @cache_mode: mode of the llcc slice
* @probe_target_ways: Probe only reserved and bonus ways on a cache miss
* @dis_cap_alloc: Disable capacity based allocation
* @retain_on_pc: Retain through power collapse
* @activate_on_init: activate the slice on init
*/
struct llcc_slice_config {
u32 usecase_id;
u32 slice_id;
u32 max_cap;
u32 priority;
bool fixed_size;
u32 bonus_ways;
u32 res_ways;
u32 cache_mode;
u32 probe_target_ways;
bool dis_cap_alloc;
bool retain_on_pc;
bool activate_on_init;
};
/**
* llcc_drv_data - Data associated with the llcc driver
* @regmap: regmap associated with the llcc device
* @cfg: pointer to the data structure for slice configuration
* @lock: mutex associated with each slice
* @cfg_size: size of the config data table
* @max_slices: max slices as read from device tree
* @bcast_off: Offset of the broadcast bank
* @num_banks: Number of llcc banks
* @bitmap: Bit map to track the active slice ids
* @offsets: Pointer to the bank offsets array
*/
struct llcc_drv_data {
struct regmap *regmap;
const struct llcc_slice_config *cfg;
struct mutex lock;
u32 cfg_size;
u32 max_slices;
u32 bcast_off;
u32 num_banks;
unsigned long *bitmap;
u32 *offsets;
};
#if IS_ENABLED(CONFIG_QCOM_LLCC)
/**
* llcc_slice_getd - get llcc slice descriptor
* @uid: usecase_id of the client
*/
struct llcc_slice_desc *llcc_slice_getd(u32 uid);
/**
* llcc_slice_putd - llcc slice descritpor
* @desc: Pointer to llcc slice descriptor
*/
void llcc_slice_putd(struct llcc_slice_desc *desc);
/**
* llcc_get_slice_id - get slice id
* @desc: Pointer to llcc slice descriptor
*/
int llcc_get_slice_id(struct llcc_slice_desc *desc);
/**
* llcc_get_slice_size - llcc slice size
* @desc: Pointer to llcc slice descriptor
*/
size_t llcc_get_slice_size(struct llcc_slice_desc *desc);
/**
* llcc_slice_activate - Activate the llcc slice
* @desc: Pointer to llcc slice descriptor
*/
int llcc_slice_activate(struct llcc_slice_desc *desc);
/**
* llcc_slice_deactivate - Deactivate the llcc slice
* @desc: Pointer to llcc slice descriptor
*/
int llcc_slice_deactivate(struct llcc_slice_desc *desc);
/**
* qcom_llcc_probe - program the sct table
* @pdev: platform device pointer
* @table: soc sct table
* @sz: Size of the config table
*/
int qcom_llcc_probe(struct platform_device *pdev,
const struct llcc_slice_config *table, u32 sz);
#else
static inline struct llcc_slice_desc *llcc_slice_getd(u32 uid)
{
return NULL;
}
static inline void llcc_slice_putd(struct llcc_slice_desc *desc)
{
};
static inline int llcc_get_slice_id(struct llcc_slice_desc *desc)
{
return -EINVAL;
}
static inline size_t llcc_get_slice_size(struct llcc_slice_desc *desc)
{
return 0;
}
static inline int llcc_slice_activate(struct llcc_slice_desc *desc)
{
return -EINVAL;
}
static inline int llcc_slice_deactivate(struct llcc_slice_desc *desc)
{
return -EINVAL;
}
static inline int qcom_llcc_probe(struct platform_device *pdev,
const struct llcc_slice_config *table, u32 sz)
{
return -ENODEV;
}
static inline int qcom_llcc_remove(struct platform_device *pdev)
{
return -ENODEV;
}
#endif
#endif

51
include/soc/qcom/rpmh.h Normal file
View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#ifndef __SOC_QCOM_RPMH_H__
#define __SOC_QCOM_RPMH_H__
#include <soc/qcom/tcs.h>
#include <linux/platform_device.h>
#if IS_ENABLED(CONFIG_QCOM_RPMH)
int rpmh_write(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n);
int rpmh_write_async(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n);
int rpmh_write_batch(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 *n);
int rpmh_flush(const struct device *dev);
int rpmh_invalidate(const struct device *dev);
#else
static inline int rpmh_write(const struct device *dev, enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n)
{ return -ENODEV; }
static inline int rpmh_write_async(const struct device *dev,
enum rpmh_state state,
const struct tcs_cmd *cmd, u32 n)
{ return -ENODEV; }
static inline int rpmh_write_batch(const struct device *dev,
enum rpmh_state state,
const struct tcs_cmd *cmd, u32 *n)
{ return -ENODEV; }
static inline int rpmh_flush(const struct device *dev)
{ return -ENODEV; }
static inline int rpmh_invalidate(const struct device *dev)
{ return -ENODEV; }
#endif /* CONFIG_QCOM_RPMH */
#endif /* __SOC_QCOM_RPMH_H__ */

56
include/soc/qcom/tcs.h Normal file
View File

@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
*/
#ifndef __SOC_QCOM_TCS_H__
#define __SOC_QCOM_TCS_H__
#define MAX_RPMH_PAYLOAD 16
/**
* rpmh_state: state for the request
*
* RPMH_SLEEP_STATE: State of the resource when the processor subsystem
* is powered down. There is no client using the
* resource actively.
* RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously
* requested before the processor was powered down.
* RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state
* is aggregated immediately.
*/
enum rpmh_state {
RPMH_SLEEP_STATE,
RPMH_WAKE_ONLY_STATE,
RPMH_ACTIVE_ONLY_STATE,
};
/**
* struct tcs_cmd: an individual request to RPMH.
*
* @addr: the address of the resource slv_id:18:16 | offset:0:15
* @data: the resource state request
* @wait: wait for this request to be complete before sending the next
*/
struct tcs_cmd {
u32 addr;
u32 data;
u32 wait;
};
/**
* struct tcs_request: A set of tcs_cmds sent together in a TCS
*
* @state: state for the request.
* @wait_for_compl: wait until we get a response from the h/w accelerator
* @num_cmds: the number of @cmds in this request
* @cmds: an array of tcs_cmds
*/
struct tcs_request {
enum rpmh_state state;
u32 wait_for_compl;
u32 num_cmds;
struct tcs_cmd *cmds;
};
#endif /* __SOC_QCOM_TCS_H__ */