Merge "msm: msm_bus: Integrate external APIs" into msm-4.8
diff --git a/Documentation/arm/msm/boot.txt b/Documentation/arm/msm/boot.txt
new file mode 100644
index 0000000..1a41cd5
--- /dev/null
+++ b/Documentation/arm/msm/boot.txt
@@ -0,0 +1,23 @@
+Introduction
+=============
+The power management integrated circuit (PMIC) records the reason the
+Application processor was powered on in Shared Memory.
+The hardware and software used is the shared memory interface. This document
+is not for the purpose of describing this interface, but to identify the
+possible values for this data item.
+
+Description
+===========
+Shared memory item (SMEM_POWER_ON_STATUS_INFO) is read to get access to
+this data. The table below identifies the possible values stored.
+
+power_on_status values set by the PMIC for power on event:
+----------------------------------------------------------
+0x01 -- keyboard power on
+0x02 -- RTC alarm
+0x04 -- cable power on
+0x08 -- SMPL
+0x10 -- Watch Dog timeout
+0x20 -- USB charger
+0x40 -- Wall charger
+0xFF -- error reading power_on_status value
diff --git a/Documentation/devicetree/bindings/input/qpnp-power-on.txt b/Documentation/devicetree/bindings/input/qpnp-power-on.txt
new file mode 100644
index 0000000..a596aa1
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/qpnp-power-on.txt
@@ -0,0 +1,199 @@
+Qualcomm QPNP power-on
+
+The qpnp-power-on is a driver which supports the power-on(PON)
+peripheral on Qualcomm PMICs. The supported functionality includes
+power on/off reason, key press/release detection, PMIC reset configurations
+and other PON specific features. The PON module supports multiple physical
+power-on (KPDPWR_N, CBLPWR) and reset (KPDPWR_N, RESIN, KPDPWR+RESIN) sources.
+This peripheral is connected to the host processor via the SPMI interface.
+
+Required properties:
+- compatible: Must be "qcom,qpnp-power-on"
+- reg: Specifies the SPMI address and size for this PON (power-on)
+ peripheral.
+- interrupts: Specifies the interrupt associated with PON.
+- interrupt-names: Specify the interrupt names associated with interrupts.
+ Must be one of "kpdpwr", "kpdpwr-bark", "resin",
+ "resin-bark", "cblpwr", "kpdpwr-resin-bark".Bark
+ interrupts are associated with system reset
+ configuration to allow default reset configuration to
+ be activated. If system reset configuration is not
+ supported then bark interrupts are nops. Additionally,
+ the "pmic-wd-bark" interrupt can be added if the system
+ needs to handle PMIC watch dog barks.
+
+Optional properties:
+- qcom,pon-dbc-delay The debounce delay for the power-key interrupt
+ specified in us.
+ Possible values for GEN1 PON are:
+ 15625, 31250, 62500, 125000, 250000, 500000,
+ 1000000 and 2000000.
+ Possible values for GEN2 PON are:
+ 62, 123, 245, 489, 977, 1954, 3907, 7813,
+ 15625, 31250, 62500, 125000 and 250000.
+ Intermediate value is rounded down to the
+ nearest valid value.
+- qcom,pon_1 ...pon_n These represent the child nodes which describe
+ the properties (reset, key) for each of the pon
+ reset source. All the child nodes are optional.
+ If none of them is specified, the driver fails
+ to register.
+- qcom,system-reset Specifies that this PON peripheral can be used
+ to reset the system. This property can only be
+ used by one device on the system. It is an
+ error to include it more than once.
+- qcom,s3-debounce The debounce delay for stage3 reset trigger in
+ secs. The values range from 0 to 128.
+- qcom,s3-src The source for stage 3 reset. It can be one of
+ "kpdpwr", "resin", "kpdpwr-or-resin" or
+ "kpdpwr-and-resin". The default value is
+ "kpdpwr-and-resin".
+- qcom,uvlo-panic If this property is present, the device will
+ set to panic during reboot if this reboot is
+ due to under voltage lock out.
+- qcom,clear-warm-reset Specifies that the WARM_RESET reason registers
+ need to be cleared for this target. The
+ property is used for the targets which have a
+ hardware feature to catch resets which aren't
+ triggered by the MSM.
+ In such cases clearing WARM_REASON registers
+ across MSM resets keeps the registers in good
+ state.
+- qcom,secondary-pon-reset Boolean property which indicates that the PON
+ peripheral is a secondary PON device which
+ needs to be configured during reset in addition
+ to the primary PON device that is configured
+ for system reset through qcom,system-reset
+ property.
+ This should not be defined along with the
+ qcom,system-reset property.
+- qcom,store-hard-reset-reason Boolean property which if set will store the
+ hardware reset reason to SOFT_RB_SPARE register
+ of the core PMIC PON peripheral.
+- qcom,warm-reset-poweroff-type Poweroff type required to be configured on
+ PS_HOLD reset control register when the system
+ goes for warm reset. If this property is not
+ specified, then the default type, warm reset
+ will be configured to PS_HOLD reset control
+ register.
+- qcom,hard-reset-poweroff-type Same description as qcom,warm-reset-poweroff-
+ type but this applies for the system hard reset
+ case.
+- qcom,shutdown-poweroff-type Same description as qcom,warm-reset-poweroff-
+ type but this applies for the system shutdown
+ case.
+
+
+All the below properties are in the sub-node section (properties of the child
+node).
+
+Sub-nodes (if defined) should belong to either a PON configuration or a
+regulator configuration.
+
+Regulator sub-node required properties:
+- regulator-name Regulator name for the PON regulator that
+ is being configured.
+- qcom,pon-spare-reg-addr Register offset from the base address of the
+ PON peripheral that needs to be configured for
+ the regulator being controlled.
+- qcom,pon-spare-reg-bit Bit position in the specified register that
+ needs to be configured for the regulator being
+ controlled.
+
+PON sub-node required properties:
+- qcom,pon-type The type of PON/RESET source. The driver
+ currently supports KPDPWR(0), RESIN(1) and
+ CBLPWR(2) pon/reset sources.
+
+PON sub-node optional properties:
+- qcom,pull-up The initial state of the reset pin under
+ consideration.
+ 0 = No pull-up
+ 1 = pull-up enabled
+ This property is set to '0' if not specified.
+- qcom,support-reset Indicates if this PON source supports
+ reset functionality.
+ 0 = Not supported
+ 1 = Supported
+ If this property is not defined, then do not
+ modify S2 reset values.
+- qcom,use-bark Specify if this pon type needs to handle bark
+ irq.
+- linux,code The input key-code associated with the reset
+ source.
+ The reset source in its default configuration
+ can be used to support standard keys.
+
+The below mentioned properties are required only when qcom,support-reset DT
+property is defined and is set to 1.
+
+- qcom,s1-timer The debounce timer for the BARK interrupt for
+ that reset source. Value is specified in ms.
+ Supported values are:
+ - 0, 32, 56, 80, 128, 184, 272, 408, 608, 904
+ 1352, 2048, 3072, 4480, 6720, 10256
+- qcom,s2-timer The debounce timer for the S2 reset specified
+ in ms. On the expiry of this timer, the PMIC
+ executes the reset sequence.
+ Supported values are:
+ - 0, 10, 50, 100, 250, 500, 1000, 2000
+- qcom,s2-type The type of reset associated with this source.
+ The supported resets are:
+ SOFT(0), WARM(1), SHUTDOWN(4), HARD(7)
+
+Example:
+ qcom,power-on@800 {
+ compatible = "qcom,qpnp-power-on";
+ reg = <0x800 0x100>;
+ interrupts = <0x0 0x8 0x0>,
+ <0x0 0x8 0x1>,
+ <0x0 0x8 0x4>,
+ <0x0 0x8 0x5>;
+ interrupt-names = "kpdpwr", "resin",
+ "resin-bark", "kpdpwr-resin-bark";
+ qcom,pon-dbc-delay = <15625>;
+ qcom,system-reset;
+ qcom,s3-debounce = <32>;
+ qcom,s3-src = "resin";
+ qcom,clear-warm-reset;
+ qcom,store-hard-reset-reason;
+
+ qcom,pon_1 {
+ qcom,pon-type = <0>;
+ qcom,pull-up = <1>;
+ linux,code = <116>;
+ };
+
+ qcom,pon_2 {
+ qcom,pon-type = <1>;
+ qcom,support-reset = <1>;
+ qcom,pull-up = <1>;
+ qcom,s1-timer = <0>;
+ qcom,s2-timer = <2000>;
+ qcom,s2-type = <1>;
+ linux,code = <114>;
+ qcom,use-bark;
+ };
+
+ qcom,pon_3 {
+ qcom,pon-type = <3>;
+ qcom,support-reset = <1>;
+ qcom,s1-timer = <6720>;
+ qcom,s2-timer = <2000>;
+ qcom,s2-type = <7>;
+ qcom,pull-up = <1>;
+ qcom,use-bark;
+ };
+ };
+ qcom,power-on@800 {
+ compatible = "qcom,qpnp-power-on";
+ reg = <0x800 0x100>;
+ qcom,secondary-pon-reset;
+ qcom,hard-reset-poweroff-type = <PON_POWER_OFF_SHUTDOWN>;
+
+ pon_perph_reg: qcom,pon_perph_reg {
+ regulator-name = "pon_spare_reg";
+ qcom,pon-spare-reg-addr = <0x8c>;
+ qcom,pon-spare-reg-bit = <1>;
+ };
+ };
diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt
index 52daff6..f74123c 100644
--- a/Documentation/sysctl/kernel.txt
+++ b/Documentation/sysctl/kernel.txt
@@ -23,8 +23,10 @@
- auto_msgmni
- bootloader_type [ X86 only ]
- bootloader_version [ X86 only ]
+- boot_reason [ ARM and ARM64 only ]
- callhome [ S390 only ]
- cap_last_cap
+- cold_boot [ ARM and ARM64 only ]
- core_pattern
- core_pipe_limit
- core_uses_pid
@@ -164,6 +166,19 @@
==============================================================
+boot_reason:
+
+ARM and ARM64 -- reason for device boot
+
+A single bit will be set in the unsigned integer value to identify the
+reason the device was booted / powered on. The value will be zero if this
+feature is not supported on the ARM device being booted.
+
+See the power-on-status field definitions in
+Documentation/arm/msm/boot.txt for Qualcomm's family of devices.
+
+==============================================================
+
callhome:
Controls the kernel's callhome behavior in case of a kernel panic.
@@ -184,6 +199,16 @@
Highest valid capability of the running kernel. Exports
CAP_LAST_CAP from the kernel.
+===============================================================
+
+cold_boot
+
+ARM and ARM64 -- indicator for system cold boot
+
+A single bit will be set in the unsigned integer value to identify
+whether the device was booted from a cold or warm state. Zero
+indicating a warm boot and one indicating a cold boot.
+
==============================================================
core_pattern:
diff --git a/arch/arm/include/asm/processor.h b/arch/arm/include/asm/processor.h
index 8a1e8e9..b2c5d79 100644
--- a/arch/arm/include/asm/processor.h
+++ b/arch/arm/include/asm/processor.h
@@ -30,6 +30,9 @@
#define STACK_TOP_MAX TASK_SIZE
#endif
+extern unsigned int boot_reason;
+extern unsigned int cold_boot;
+
struct debug_info {
#ifdef CONFIG_HAVE_HW_BREAKPOINT
struct perf_event *hbp[ARM_MAX_HBP_SLOTS];
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index ccf79ca..01efdf478 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -116,6 +116,12 @@ EXPORT_SYMBOL(elf_hwcap2);
char* (*arch_read_hardware_id)(void);
EXPORT_SYMBOL(arch_read_hardware_id);
+unsigned int boot_reason;
+EXPORT_SYMBOL(boot_reason);
+
+unsigned int cold_boot;
+EXPORT_SYMBOL(cold_boot);
+
#ifdef MULTI_CPU
struct processor processor __ro_after_init;
#endif
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 3aff389..820c23d2 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -606,6 +606,18 @@
Say Y here to experiment with turning CPUs off and on. CPUs
can be controlled through /sys/devices/system/cpu.
+# The GPIO number here must be sorted by descending number. In case of
+# a multiplatform kernel, we just want the highest value required by the
+# selected platforms.
+config ARCH_NR_GPIO
+ int
+ default 1024 if ARCH_QCOM
+ default 256
+ help
+ Maximum number of GPIOs in the system.
+
+ If unsure, leave the default value.
+
# Common NUMA Features
config NUMA
bool "Numa Memory Allocation and Scheduler Support"
diff --git a/arch/arm64/boot/dts/qcom/msmskunk.dtsi b/arch/arm64/boot/dts/qcom/msmskunk.dtsi
index c15a508..f53280a 100644
--- a/arch/arm64/boot/dts/qcom/msmskunk.dtsi
+++ b/arch/arm64/boot/dts/qcom/msmskunk.dtsi
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -16,6 +16,7 @@
#include <dt-bindings/clock/qcom,dispcc-skunk.h>
#include <dt-bindings/clock/qcom,gpucc-skunk.h>
#include <dt-bindings/clock/qcom,videocc-skunk.h>
+#include <dt-bindings/clock/qcom,cpucc-skunk.h>
#include <dt-bindings/regulator/qcom,rpmh-regulator.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/soc/qcom,tcs-mbox.h>
@@ -497,6 +498,13 @@
#reset-cells = <1>;
};
+ clock_cpucc: qcom,cpucc {
+ compatible = "qcom,dummycc";
+ clock-output-names = "cpucc_clocks";
+ #clock-cells = <1>;
+ #reset-cells = <1>;
+ };
+
ufsphy_mem: ufsphy_mem@1d87000 {
reg = <0x1d87000 0xda8>; /* PHY regs */
reg-names = "phy_mem";
@@ -690,9 +698,9 @@
interrupts = <0 266 1>;
vdd_cx-supply = <&pm8998_s9_level>;
- vdd_cx-voltage = <RPMH_REGULATOR_LEVEL_MAX>;
+ vdd_cx-voltage = <RPMH_REGULATOR_LEVEL_TURBO>;
vdd_mx-supply = <&pm8998_s6_level>;
- vdd_mx-uV = <RPMH_REGULATOR_LEVEL_MAX>;
+ vdd_mx-uV = <RPMH_REGULATOR_LEVEL_TURBO>;
qcom,firmware-name = "modem";
qcom,pil-self-auth;
qcom,sysmon-id = <0>;
@@ -721,7 +729,7 @@
vdd_cx-supply = <&pm8998_s9_level>;
qcom,proxy-reg-names = "vdd_cx";
- qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_MAX 100000>;
+ qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_TURBO 100000>;
clocks = <&clock_gcc RPMH_CXO_CLK>;
clock-names = "xo";
@@ -753,7 +761,7 @@
vdd_cx-supply = <&pm8998_l27_level>;
vdd_px-supply = <&pm8998_lvs2>;
- qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_MAX 0>;
+ qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_TURBO 0>;
qcom,proxy-reg-names = "vdd_cx", "vdd_px";
qcom,keep-proxy-regs-on;
@@ -802,9 +810,9 @@
vdd_cx-supply = <&pm8998_s9_level>;
qcom,proxy-reg-names = "vdd_cx";
- qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_MAX 100000>;
+ qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_TURBO 100000>;
vdd_mx-supply = <&pm8998_s6_level>;
- vdd_mx-uV = <RPMH_REGULATOR_LEVEL_MAX 100000>;
+ vdd_mx-uV = <RPMH_REGULATOR_LEVEL_TURBO 100000>;
clocks = <&clock_gcc RPMH_CXO_CLK>;
clock-names = "xo";
@@ -837,7 +845,7 @@
vdd_cx-supply = <&pm8998_s9_level>;
qcom,proxy-reg-names = "vdd_cx";
- qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_MAX 100000>;
+ qcom,vdd_cx-uV-uA = <RPMH_REGULATOR_LEVEL_TURBO 100000>;
clocks = <&clock_gcc RPMH_CXO_CLK>;
clock-names = "xo";
diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h
index 60e3482..a4af9f0 100644
--- a/arch/arm64/include/asm/processor.h
+++ b/arch/arm64/include/asm/processor.h
@@ -49,6 +49,9 @@
extern phys_addr_t arm64_dma_phys_limit;
#define ARCH_LOW_ADDRESS_LIMIT (arm64_dma_phys_limit - 1)
+extern unsigned int boot_reason;
+extern unsigned int cold_boot;
+
struct debug_info {
/* Have we suspended stepping by a debugger? */
int suspended_step;
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
index 75f0efe..9f79515 100644
--- a/arch/arm64/kernel/setup.c
+++ b/arch/arm64/kernel/setup.c
@@ -67,6 +67,12 @@
phys_addr_t __fdt_pointer __initdata;
+unsigned int boot_reason;
+EXPORT_SYMBOL(boot_reason);
+
+unsigned int cold_boot;
+EXPORT_SYMBOL(cold_boot);
+
const char *machine_name;
/*
* Standard memory resources
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 22d1760..958e255 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -294,7 +294,8 @@ static const char * const fw_path[] = {
"/lib/firmware/updates/" UTS_RELEASE,
"/lib/firmware/updates",
"/lib/firmware/" UTS_RELEASE,
- "/lib/firmware"
+ "/lib/firmware",
+ "/firmware/image"
};
/*
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
index c05dabd..59e9899 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
@@ -128,7 +128,7 @@ static int dsi_display_debugfs_init(struct dsi_display *display)
static int dsi_dipslay_debugfs_deinit(struct dsi_display *display)
{
- debugfs_remove(display->root);
+ debugfs_remove_recursive(display->root);
return 0;
}
@@ -999,6 +999,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
src->byte_clk = devm_clk_get(&display->pdev->dev, "src_byte_clk");
if (IS_ERR_OR_NULL(src->byte_clk)) {
rc = PTR_ERR(src->byte_clk);
+ src->byte_clk = NULL;
pr_err("failed to get src_byte_clk, rc=%d\n", rc);
goto error;
}
@@ -1006,6 +1007,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
src->pixel_clk = devm_clk_get(&display->pdev->dev, "src_pixel_clk");
if (IS_ERR_OR_NULL(src->pixel_clk)) {
rc = PTR_ERR(src->pixel_clk);
+ src->pixel_clk = NULL;
pr_err("failed to get src_pixel_clk, rc=%d\n", rc);
goto error;
}
@@ -1014,6 +1016,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
if (IS_ERR_OR_NULL(mux->byte_clk)) {
rc = PTR_ERR(mux->byte_clk);
pr_err("failed to get mux_byte_clk, rc=%d\n", rc);
+ mux->byte_clk = NULL;
/*
* Skip getting rest of clocks since one failed. This is a
* non-critical failure since these clocks are requied only for
@@ -1026,6 +1029,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
mux->pixel_clk = devm_clk_get(&display->pdev->dev, "mux_pixel_clk");
if (IS_ERR_OR_NULL(mux->pixel_clk)) {
rc = PTR_ERR(mux->pixel_clk);
+ mux->pixel_clk = NULL;
pr_err("failed to get mux_pixel_clk, rc=%d\n", rc);
/*
* Skip getting rest of clocks since one failed. This is a
@@ -1039,6 +1043,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
shadow->byte_clk = devm_clk_get(&display->pdev->dev, "shadow_byte_clk");
if (IS_ERR_OR_NULL(shadow->byte_clk)) {
rc = PTR_ERR(shadow->byte_clk);
+ shadow->byte_clk = NULL;
pr_err("failed to get shadow_byte_clk, rc=%d\n", rc);
/*
* Skip getting rest of clocks since one failed. This is a
@@ -1053,6 +1058,7 @@ static int dsi_display_clocks_init(struct dsi_display *display)
"shadow_pixel_clk");
if (IS_ERR_OR_NULL(shadow->pixel_clk)) {
rc = PTR_ERR(shadow->pixel_clk);
+ shadow->pixel_clk = NULL;
pr_err("failed to get shadow_pixel_clk, rc=%d\n", rc);
/*
* Skip getting rest of clocks since one failed. This is a
@@ -1888,6 +1894,8 @@ int dsi_display_unbind(struct dsi_display *display)
display->name, i, rc);
}
+ (void)dsi_dipslay_debugfs_deinit(display);
+
mutex_unlock(&display->display_lock);
return rc;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_connector.h b/drivers/gpu/drm/msm/sde/sde_connector.h
index 403160c..cca934d 100644
--- a/drivers/gpu/drm/msm/sde/sde_connector.h
+++ b/drivers/gpu/drm/msm/sde/sde_connector.h
@@ -239,6 +239,20 @@ struct sde_connector_state {
((S) ? to_sde_connector_state((S))->out_fb : 0)
/**
+ * sde_connector_get_topology_name - helper accessor to retrieve topology_name
+ * @connector: pointer to drm connector
+ * Returns: value of the CONNECTOR_PROP_TOPOLOGY_NAME property or 0
+ */
+static inline uint64_t sde_connector_get_topology_name(
+ struct drm_connector *connector)
+{
+ if (!connector || !connector->state)
+ return 0;
+ return sde_connector_get_property(connector->state,
+ CONNECTOR_PROP_TOPOLOGY_NAME);
+}
+
+/**
* sde_connector_init - create drm connector object for a given display
* @dev: Pointer to drm device struct
* @encoder: Pointer to associated encoder
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 535b7d6..fd57404 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -350,6 +350,9 @@ static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc,
if (!conn) {
SDE_ERROR_ENC(sde_enc, "failed to find attached connector\n");
return;
+ } else if (!conn->state) {
+ SDE_ERROR_ENC(sde_enc, "invalid connector state\n");
+ return;
}
/* Reserve dynamic resources now. Indicating non-AtomicTest phase */
@@ -364,8 +367,11 @@ static void sde_encoder_virt_mode_set(struct drm_encoder *drm_enc,
for (i = 0; i < sde_enc->num_phys_encs; i++) {
struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
- if (phys && phys->ops.mode_set)
- phys->ops.mode_set(phys, mode, adj_mode);
+ if (phys) {
+ phys->connector = conn->state->connector;
+ if (phys->ops.mode_set)
+ phys->ops.mode_set(phys, mode, adj_mode);
+ }
}
}
@@ -452,11 +458,10 @@ static void sde_encoder_virt_disable(struct drm_encoder *drm_enc)
for (i = 0; i < sde_enc->num_phys_encs; i++) {
struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
- if (phys && phys->ops.disable && !phys->ops.is_master(phys)) {
- phys->ops.disable(phys);
-
- atomic_set(&phys->vsync_cnt, 0);
- atomic_set(&phys->underrun_cnt, 0);
+ if (phys) {
+ if (phys->ops.disable && !phys->ops.is_master(phys))
+ phys->ops.disable(phys);
+ phys->connector = NULL;
}
}
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
index 28548f3..22d187e 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
@@ -147,6 +147,7 @@ enum sde_intr_idx {
* tied to a specific panel / sub-panel. Abstract type, sub-classed by
* phys_vid or phys_cmd for video mode or command mode encs respectively.
* @parent: Pointer to the containing virtual encoder
+ * @connector: If a mode is set, cached pointer to the active connector
* @ops: Operations exposed to the virtual encoder
* @parent_ops: Callbacks exposed by the parent to the phys_enc
* @hw_mdptop: Hardware interface to the top registers
@@ -160,7 +161,6 @@ enum sde_intr_idx {
* @intf_mode: Interface mode
* @intf_idx: Interface index on sde hardware
* @spin_lock: Lock for IRQ purposes
- * @mode_3d: 3D mux configuration
* @enable_state: Enable state tracking
* @vblank_refcount: Reference count of vblank request
* @vsync_cnt: Vsync count for the physical encoder
@@ -168,6 +168,7 @@ enum sde_intr_idx {
*/
struct sde_encoder_phys {
struct drm_encoder *parent;
+ struct drm_connector *connector;
struct sde_encoder_phys_ops ops;
struct sde_encoder_virt_ops parent_ops;
struct sde_hw_mdp *hw_mdptop;
@@ -180,7 +181,6 @@ struct sde_encoder_phys {
enum sde_intf_mode intf_mode;
enum sde_intf intf_idx;
spinlock_t spin_lock;
- enum sde_3d_blend_mode mode_3d;
enum sde_enc_enable_state enable_state;
atomic_t vblank_refcount;
atomic_t vsync_cnt;
@@ -338,4 +338,18 @@ void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc,
*/
void sde_encoder_helper_trigger_start(struct sde_encoder_phys *phys_enc);
+
+static inline enum sde_3d_blend_mode sde_encoder_helper_get_3d_blend_mode(
+ struct sde_encoder_phys *phys_enc)
+{
+ enum sde_rm_topology_name topology;
+
+ topology = sde_connector_get_topology_name(phys_enc->connector);
+ if (phys_enc->split_role == ENC_ROLE_SOLO &&
+ topology == SDE_RM_TOPOLOGY_DUALPIPEMERGE)
+ return BLEND_3D_H_ROW_INT;
+
+ return BLEND_3D_NONE;
+}
+
#endif /* __sde_encoder_phys_H__ */
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
index 79da71b..3f478fa 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
@@ -341,9 +341,10 @@ static void sde_encoder_phys_cmd_pingpong_config(
drm_mode_debug_printmodeline(&phys_enc->cached_mode);
intf_cfg.intf = cmd_enc->intf_idx;
- intf_cfg.mode_3d = phys_enc->mode_3d;
intf_cfg.intf_mode_sel = SDE_CTL_MODE_SEL_CMD;
intf_cfg.stream_sel = cmd_enc->stream_sel;
+ intf_cfg.mode_3d = sde_encoder_helper_get_3d_blend_mode(phys_enc);
+
phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl, &intf_cfg);
sde_encoder_phys_cmd_tearcheck_config(phys_enc);
@@ -649,7 +650,6 @@ struct sde_encoder_phys *sde_encoder_phys_cmd_init(
phys_enc->split_role = p->split_role;
phys_enc->intf_mode = INTF_MODE_CMD;
spin_lock_init(&phys_enc->spin_lock);
- phys_enc->mode_3d = BLEND_3D_NONE;
cmd_enc->stream_sel = 0;
phys_enc->enable_state = SDE_ENC_DISABLED;
atomic_set(&cmd_enc->pending_cnt, 0);
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
index 8c5e604..b94f64f 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
@@ -248,22 +248,26 @@ static bool sde_encoder_phys_vid_mode_fixup(
static void sde_encoder_phys_vid_setup_timing_engine(
struct sde_encoder_phys *phys_enc)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
- struct drm_display_mode mode = phys_enc->cached_mode;
+ struct sde_encoder_phys_vid *vid_enc;
+ struct drm_display_mode mode;
struct intf_timing_params timing_params = { 0 };
const struct sde_format *fmt = NULL;
u32 fmt_fourcc = DRM_FORMAT_RGB888;
unsigned long lock_flags;
struct sde_hw_intf_cfg intf_cfg = { 0 };
- if (!phys_enc ||
- !vid_enc->hw_intf->ops.setup_timing_gen ||
- !phys_enc->hw_ctl->ops.setup_intf_cfg) {
+ if (!phys_enc || !phys_enc->hw_ctl->ops.setup_intf_cfg) {
SDE_ERROR("invalid encoder %d\n", phys_enc != 0);
return;
}
+ mode = phys_enc->cached_mode;
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
+ if (!vid_enc->hw_intf->ops.setup_timing_gen) {
+ SDE_ERROR("timing engine setup is not supported\n");
+ return;
+ }
+
SDE_DEBUG_VIDENC(vid_enc, "enabling mode:\n");
drm_mode_debug_printmodeline(&mode);
@@ -286,10 +290,9 @@ static void sde_encoder_phys_vid_setup_timing_engine(
SDE_DEBUG_VIDENC(vid_enc, "fmt_fourcc 0x%X\n", fmt_fourcc);
intf_cfg.intf = vid_enc->hw_intf->idx;
- intf_cfg.wb = SDE_NONE;
- intf_cfg.mode_3d = phys_enc->mode_3d;
intf_cfg.intf_mode_sel = SDE_CTL_MODE_SEL_VID;
intf_cfg.stream_sel = 0; /* Don't care value for video mode */
+ intf_cfg.mode_3d = sde_encoder_helper_get_3d_blend_mode(phys_enc);
spin_lock_irqsave(&phys_enc->spin_lock, lock_flags);
vid_enc->hw_intf->ops.setup_timing_gen(vid_enc->hw_intf,
@@ -367,8 +370,7 @@ static int sde_encoder_phys_vid_register_irq(struct sde_encoder_phys *phys_enc,
enum sde_intr_type intr_type, int *irq_idx,
void (*irq_func)(void *, int), const char *irq_name)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
struct sde_irq_callback irq_cb;
int ret = 0;
@@ -377,6 +379,7 @@ static int sde_encoder_phys_vid_register_irq(struct sde_encoder_phys *phys_enc,
return -EINVAL;
}
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
*irq_idx = sde_core_irq_idx_lookup(phys_enc->sde_kms, intr_type,
vid_enc->hw_intf->idx);
if (*irq_idx < 0) {
@@ -418,14 +421,14 @@ static int sde_encoder_phys_vid_register_irq(struct sde_encoder_phys *phys_enc,
static int sde_encoder_phys_vid_unregister_irq(
struct sde_encoder_phys *phys_enc, int irq_idx)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
goto end;
}
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
sde_core_irq_disable(phys_enc->sde_kms, &irq_idx, 1);
sde_core_irq_register_callback(phys_enc->sde_kms, irq_idx, NULL);
@@ -441,17 +444,17 @@ static void sde_encoder_phys_vid_mode_set(
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
struct sde_rm *rm = &phys_enc->sde_kms->rm;
struct sde_rm_hw_iter iter;
int i, instance;
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
}
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
phys_enc->cached_mode = *adj_mode;
SDE_DEBUG_VIDENC(vid_enc, "caching mode:\n");
drm_mode_debug_printmodeline(adj_mode);
@@ -478,15 +481,16 @@ static int sde_encoder_phys_vid_control_vblank_irq(
struct sde_encoder_phys *phys_enc,
bool enable)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
int ret = 0;
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return -EINVAL;
}
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
+
/* Slave encoders don't report vblank */
if (!sde_encoder_phys_vid_is_master(phys_enc))
return 0;
@@ -517,17 +521,21 @@ static int sde_encoder_phys_vid_control_vblank_irq(
static void sde_encoder_phys_vid_enable(struct sde_encoder_phys *phys_enc)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
- struct sde_hw_intf *intf = vid_enc->hw_intf;
- struct sde_hw_ctl *ctl = phys_enc->hw_ctl;
+ struct sde_encoder_phys_vid *vid_enc;
+ struct sde_hw_intf *intf;
+ struct sde_hw_ctl *ctl;
u32 flush_mask = 0;
int ret;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
- } else if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
+ }
+
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
+ intf = vid_enc->hw_intf;
+ ctl = phys_enc->hw_ctl;
+ if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
SDE_ERROR("invalid hw_intf %d hw_ctl %d\n",
vid_enc->hw_intf != 0, phys_enc->hw_ctl != 0);
return;
@@ -574,13 +582,15 @@ static void sde_encoder_phys_vid_enable(struct sde_encoder_phys *phys_enc)
static void sde_encoder_phys_vid_disable(struct sde_encoder_phys *phys_enc)
{
unsigned long lock_flags;
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
- } else if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
+ }
+
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
+ if (!vid_enc->hw_intf || !phys_enc->hw_ctl) {
SDE_ERROR("invalid hw_intf %d hw_ctl %d\n",
vid_enc->hw_intf != 0, phys_enc->hw_ctl != 0);
return;
@@ -624,13 +634,14 @@ static void sde_encoder_phys_vid_disable(struct sde_encoder_phys *phys_enc)
static void sde_encoder_phys_vid_destroy(struct sde_encoder_phys *phys_enc)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
}
+
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
SDE_DEBUG_VIDENC(vid_enc, "\n");
kfree(vid_enc);
}
@@ -640,14 +651,20 @@ static void sde_encoder_phys_vid_get_hw_resources(
struct sde_encoder_hw_resources *hw_res,
struct drm_connector_state *conn_state)
{
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
- if (!phys_enc || !hw_res || !vid_enc->hw_intf) {
+ if (!phys_enc || !hw_res) {
SDE_ERROR("invalid arg(s), enc %d hw_res %d conn_state %d\n",
phys_enc != 0, hw_res != 0, conn_state != 0);
return;
}
+
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
+ if (!vid_enc->hw_intf) {
+ SDE_ERROR("invalid arg(s), hw_intf\n");
+ return;
+ }
+
SDE_DEBUG_VIDENC(vid_enc, "\n");
hw_res->intfs[vid_enc->hw_intf->idx - INTF_0] = INTF_MODE_VIDEO;
}
@@ -701,13 +718,14 @@ static void sde_encoder_phys_vid_handle_post_kickoff(
struct sde_encoder_phys *phys_enc)
{
unsigned long lock_flags;
- struct sde_encoder_phys_vid *vid_enc =
- to_sde_encoder_phys_vid(phys_enc);
+ struct sde_encoder_phys_vid *vid_enc;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
}
+
+ vid_enc = to_sde_encoder_phys_vid(phys_enc);
SDE_DEBUG_VIDENC(vid_enc, "enable_state %d\n", phys_enc->enable_state);
/*
@@ -802,11 +820,6 @@ struct sde_encoder_phys *sde_encoder_phys_vid_init(
spin_lock_init(&phys_enc->spin_lock);
init_completion(&vid_enc->vblank_completion);
atomic_set(&phys_enc->vblank_refcount, 0);
-
- DRM_INFO_ONCE("intf %d: 3d blend modes not yet supported\n",
- vid_enc->hw_intf->idx);
- phys_enc->mode_3d = BLEND_3D_NONE;
-
phys_enc->enable_state = SDE_ENC_DISABLED;
SDE_DEBUG_VIDENC(vid_enc, "created intf idx:%d\n", p->intf_idx);
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
index 32a25e4..665b044 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
@@ -278,6 +278,7 @@ static void sde_encoder_phys_wb_setup_cdp(struct sde_encoder_phys *phys_enc)
intf_cfg->intf = SDE_NONE;
intf_cfg->wb = hw_wb->idx;
+ intf_cfg->mode_3d = sde_encoder_helper_get_3d_blend_mode(phys_enc);
if (phys_enc->hw_ctl && phys_enc->hw_ctl->ops.setup_intf_cfg)
phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl,
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index 34e710d..5895158 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -102,163 +102,163 @@ flg, fm, np) \
static const struct sde_format sde_format_map[] = {
INTERLEAVED_RGB_FMT(ARGB8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
true, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(ABGR8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
+ C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
true, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBA8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
true, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRA8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
true, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRX8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
false, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(XRGB8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
false, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBX8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
false, 4, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGB888,
0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3,
false, 3, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGR888,
0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3,
false, 3, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGB565,
0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGR565,
0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(ARGB1555,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(ABGR1555,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
+ C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBA5551,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRA5551,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(XRGB1555,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(XBGR1555,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
+ C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBX5551,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRX5551,
COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(ARGB4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(ABGR4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
+ C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBA4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRA4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
true, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(XRGB4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
+ C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(XBGR4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
+ C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(RGBX4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4,
+ C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
INTERLEAVED_RGB_FMT(BGRX4444,
COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT,
- C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4,
+ C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4,
false, 2, 0,
SDE_FETCH_LINEAR, 1),
@@ -312,13 +312,13 @@ static const struct sde_format sde_format_map[] = {
PLANAR_YUV_FMT(YUV420,
0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C2_R_Cr, C1_B_Cb, C0_G_Y,
+ C0_G_Y, C1_B_Cb, C2_R_Cr,
false, SDE_CHROMA_420, 1, SDE_FORMAT_FLAG_YUV,
SDE_FETCH_LINEAR, 3),
PLANAR_YUV_FMT(YVU420,
0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
- C1_B_Cb, C2_R_Cr, C0_G_Y,
+ C0_G_Y, C2_R_Cr, C1_B_Cb,
false, SDE_CHROMA_420, 1, SDE_FORMAT_FLAG_YUV,
SDE_FETCH_LINEAR, 3),
};
@@ -330,19 +330,19 @@ static const struct sde_format sde_format_map[] = {
* the data will be passed by user-space.
*/
static const struct sde_format sde_format_map_ubwc[] = {
- INTERLEAVED_RGB_FMT(BGR565,
+ INTERLEAVED_RGB_FMT(RGB565,
0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT,
C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3,
false, 2, 0,
SDE_FETCH_UBWC, 2),
- INTERLEAVED_RGB_FMT(ABGR8888,
+ INTERLEAVED_RGB_FMT(RGBA8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
true, 4, 0,
SDE_FETCH_UBWC, 2),
- INTERLEAVED_RGB_FMT(XBGR8888,
+ INTERLEAVED_RGB_FMT(RGBX8888,
COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT,
C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4,
false, 4, 0,
@@ -447,12 +447,12 @@ static int _sde_format_get_plane_sizes_ubwc(
ALIGN(DIV_ROUND_UP(height / 2, uv_tile_height), 16),
4096);
- } else if (fmt->base.pixel_format == DRM_FORMAT_ABGR8888 ||
- fmt->base.pixel_format == DRM_FORMAT_XBGR8888 ||
- fmt->base.pixel_format == DRM_FORMAT_BGR565) {
+ } else if (fmt->base.pixel_format == DRM_FORMAT_RGBA8888 ||
+ fmt->base.pixel_format == DRM_FORMAT_RGBX8888 ||
+ fmt->base.pixel_format == DRM_FORMAT_RGB565) {
uint32_t stride_alignment, aligned_bitstream_width;
- if (fmt->base.pixel_format == DRM_FORMAT_BGR565)
+ if (fmt->base.pixel_format == DRM_FORMAT_RGB565)
stride_alignment = 128;
else
stride_alignment = 64;
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
index 4e8033a..2f1bac7 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
@@ -21,7 +21,6 @@
#define CTL_TOP 0x014
#define CTL_FLUSH 0x018
#define CTL_START 0x01C
-#define CTL_PACK_3D 0x020
#define CTL_SW_RESET 0x030
#define CTL_LAYER_EXTN_OFFSET 0x40
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_top.c b/drivers/gpu/drm/msm/sde/sde_hw_top.c
index 8950a60..10d3917 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_top.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_top.c
@@ -141,34 +141,31 @@ struct sde_hw_mdp *sde_hw_mdptop_init(enum sde_mdp idx,
void __iomem *addr,
const struct sde_mdss_cfg *m)
{
- static struct sde_hw_mdp *c;
+ struct sde_hw_mdp *mdp;
const struct sde_mdp_cfg *cfg;
- /* mdp top is singleton */
- if (c)
- return c;
-
- c = kzalloc(sizeof(*c), GFP_KERNEL);
- if (!c)
+ mdp = kzalloc(sizeof(*mdp), GFP_KERNEL);
+ if (!mdp)
return ERR_PTR(-ENOMEM);
- cfg = _top_offset(idx, m, addr, &c->hw);
+ cfg = _top_offset(idx, m, addr, &mdp->hw);
if (IS_ERR_OR_NULL(cfg)) {
- kfree(c);
+ kfree(mdp);
return ERR_PTR(-EINVAL);
}
/*
* Assign ops
*/
- c->idx = idx;
- c->cap = cfg;
- _setup_mdp_ops(&c->ops, c->cap->features);
+ mdp->idx = idx;
+ mdp->cap = cfg;
+ _setup_mdp_ops(&mdp->ops, mdp->cap->features);
/*
* Perform any default initialization for the intf
*/
- return c;
+
+ return mdp;
}
void sde_hw_mdp_destroy(struct sde_hw_mdp *mdp)
diff --git a/drivers/gpu/drm/msm/sde_power_handle.c b/drivers/gpu/drm/msm/sde_power_handle.c
index f8ba442..e2e8e60 100644
--- a/drivers/gpu/drm/msm/sde_power_handle.c
+++ b/drivers/gpu/drm/msm/sde_power_handle.c
@@ -220,9 +220,9 @@ static int sde_power_parse_dt_clock(struct platform_device *pdev,
{
u32 i = 0, rc = 0;
const char *clock_name;
- u32 clock_rate;
- u32 clock_max_rate;
- int num_clk;
+ u32 clock_rate = 0;
+ u32 clock_max_rate = 0;
+ int num_clk = 0;
if (!pdev || !mp) {
pr_err("invalid input param pdev:%pK mp:%pK\n", pdev, mp);
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 94360fe..78e84c6 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -164,6 +164,15 @@
To compile this driver as a module, choose M here: the
module will be called pmic8xxx-pwrkey.
+config INPUT_QPNP_POWER_ON
+ tristate "QPNP PMIC Power-on support"
+ depends on SPMI
+ help
+ This option enables device driver support for the power-on
+ functionality of Qualcomm Technologies, Inc. PNP PMICs. It supports
+ reporting the change in status of the KPDPWR_N line (connected to the
+ power-key) as well as reset features.
+
config INPUT_SPARCSPKR
tristate "SPARC Speaker support"
depends on PCI && SPARC64
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 64bf231..d55e4a6 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -59,6 +59,7 @@
obj-$(CONFIG_INPUT_PM8941_PWRKEY) += pm8941-pwrkey.o
obj-$(CONFIG_INPUT_PM8XXX_VIBRATOR) += pm8xxx-vibrator.o
obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o
+obj-$(CONFIG_INPUT_QPNP_POWER_ON) += qpnp-power-on.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
diff --git a/drivers/input/misc/qpnp-power-on.c b/drivers/input/misc/qpnp-power-on.c
new file mode 100644
index 0000000..e1c16aa
--- /dev/null
+++ b/drivers/input/misc/qpnp-power-on.c
@@ -0,0 +1,2359 @@
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/input.h>
+#include <linux/log2.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/input/qpnp-power-on.h>
+#include <linux/power_supply.h>
+
+#define PMIC_VER_8941 0x01
+#define PMIC_VERSION_REG 0x0105
+#define PMIC_VERSION_REV4_REG 0x0103
+
+#define PMIC8941_V1_REV4 0x01
+#define PMIC8941_V2_REV4 0x02
+#define PON_PRIMARY 0x01
+#define PON_SECONDARY 0x02
+#define PON_1REG 0x03
+#define PON_GEN2_PRIMARY 0x04
+#define PON_GEN2_SECONDARY 0x05
+
+#define PON_OFFSET(subtype, offset_gen1, offset_gen2) \
+ (((subtype == PON_PRIMARY) || \
+ (subtype == PON_SECONDARY) || \
+ (subtype == PON_1REG)) ? offset_gen1 : offset_gen2)
+
+/* Common PNP defines */
+#define QPNP_PON_REVISION2(pon) ((pon)->base + 0x01)
+#define QPNP_PON_PERPH_SUBTYPE(pon) ((pon)->base + 0x05)
+
+/* PON common register addresses */
+#define QPNP_PON_RT_STS(pon) ((pon)->base + 0x10)
+#define QPNP_PON_PULL_CTL(pon) ((pon)->base + 0x70)
+#define QPNP_PON_DBC_CTL(pon) ((pon)->base + 0x71)
+
+/* PON/RESET sources register addresses */
+#define QPNP_PON_REASON1(pon) \
+ ((pon)->base + PON_OFFSET((pon)->subtype, 0x8, 0xC0))
+#define QPNP_PON_WARM_RESET_REASON1(pon) \
+ ((pon)->base + PON_OFFSET((pon)->subtype, 0xA, 0xC2))
+#define QPNP_POFF_REASON1(pon) \
+ ((pon)->base + PON_OFFSET((pon)->subtype, 0xC, 0xC5))
+#define QPNP_PON_WARM_RESET_REASON2(pon) ((pon)->base + 0xB)
+#define QPNP_PON_OFF_REASON(pon) ((pon)->base + 0xC7)
+#define QPNP_FAULT_REASON1(pon) ((pon)->base + 0xC8)
+#define QPNP_S3_RESET_REASON(pon) ((pon)->base + 0xCA)
+#define QPNP_PON_KPDPWR_S1_TIMER(pon) ((pon)->base + 0x40)
+#define QPNP_PON_KPDPWR_S2_TIMER(pon) ((pon)->base + 0x41)
+#define QPNP_PON_KPDPWR_S2_CNTL(pon) ((pon)->base + 0x42)
+#define QPNP_PON_KPDPWR_S2_CNTL2(pon) ((pon)->base + 0x43)
+#define QPNP_PON_RESIN_S1_TIMER(pon) ((pon)->base + 0x44)
+#define QPNP_PON_RESIN_S2_TIMER(pon) ((pon)->base + 0x45)
+#define QPNP_PON_RESIN_S2_CNTL(pon) ((pon)->base + 0x46)
+#define QPNP_PON_RESIN_S2_CNTL2(pon) ((pon)->base + 0x47)
+#define QPNP_PON_KPDPWR_RESIN_S1_TIMER(pon) ((pon)->base + 0x48)
+#define QPNP_PON_KPDPWR_RESIN_S2_TIMER(pon) ((pon)->base + 0x49)
+#define QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon) ((pon)->base + 0x4A)
+#define QPNP_PON_KPDPWR_RESIN_S2_CNTL2(pon) ((pon)->base + 0x4B)
+#define QPNP_PON_PS_HOLD_RST_CTL(pon) ((pon)->base + 0x5A)
+#define QPNP_PON_PS_HOLD_RST_CTL2(pon) ((pon)->base + 0x5B)
+#define QPNP_PON_WD_RST_S2_CTL(pon) ((pon)->base + 0x56)
+#define QPNP_PON_WD_RST_S2_CTL2(pon) ((pon)->base + 0x57)
+#define QPNP_PON_S3_SRC(pon) ((pon)->base + 0x74)
+#define QPNP_PON_S3_DBC_CTL(pon) ((pon)->base + 0x75)
+#define QPNP_PON_SMPL_CTL(pon) ((pon)->base + 0x7F)
+#define QPNP_PON_TRIGGER_EN(pon) ((pon)->base + 0x80)
+#define QPNP_PON_XVDD_RB_SPARE(pon) ((pon)->base + 0x8E)
+#define QPNP_PON_SOFT_RB_SPARE(pon) ((pon)->base + 0x8F)
+#define QPNP_PON_SEC_ACCESS(pon) ((pon)->base + 0xD0)
+
+#define QPNP_PON_SEC_UNLOCK 0xA5
+
+#define QPNP_PON_WARM_RESET_TFT BIT(4)
+
+#define QPNP_PON_RESIN_PULL_UP BIT(0)
+#define QPNP_PON_KPDPWR_PULL_UP BIT(1)
+#define QPNP_PON_CBLPWR_PULL_UP BIT(2)
+#define QPNP_PON_FAULT_PULL_UP BIT(4)
+#define QPNP_PON_S2_CNTL_EN BIT(7)
+#define QPNP_PON_S2_RESET_ENABLE BIT(7)
+#define QPNP_PON_DELAY_BIT_SHIFT 6
+#define QPNP_PON_GEN2_DELAY_BIT_SHIFT 14
+
+#define QPNP_PON_S1_TIMER_MASK (0xF)
+#define QPNP_PON_S2_TIMER_MASK (0x7)
+#define QPNP_PON_S2_CNTL_TYPE_MASK (0xF)
+
+#define QPNP_PON_DBC_DELAY_MASK(pon) \
+ PON_OFFSET((pon)->subtype, 0x7, 0xF)
+
+#define QPNP_PON_KPDPWR_N_SET BIT(0)
+#define QPNP_PON_RESIN_N_SET BIT(1)
+#define QPNP_PON_CBLPWR_N_SET BIT(2)
+#define QPNP_PON_RESIN_BARK_N_SET BIT(4)
+#define QPNP_PON_KPDPWR_RESIN_BARK_N_SET BIT(5)
+
+#define QPNP_PON_WD_EN BIT(7)
+#define QPNP_PON_RESET_EN BIT(7)
+#define QPNP_PON_POWER_OFF_MASK 0xF
+#define QPNP_GEN2_POFF_SEQ BIT(7)
+#define QPNP_GEN2_FAULT_SEQ BIT(6)
+#define QPNP_GEN2_S3_RESET_SEQ BIT(5)
+
+#define QPNP_PON_S3_SRC_KPDPWR 0
+#define QPNP_PON_S3_SRC_RESIN 1
+#define QPNP_PON_S3_SRC_KPDPWR_AND_RESIN 2
+#define QPNP_PON_S3_SRC_KPDPWR_OR_RESIN 3
+#define QPNP_PON_S3_SRC_MASK 0x3
+#define QPNP_PON_HARD_RESET_MASK GENMASK(7, 5)
+
+#define QPNP_PON_UVLO_DLOAD_EN BIT(7)
+#define QPNP_PON_SMPL_EN BIT(7)
+
+/* Ranges */
+#define QPNP_PON_S1_TIMER_MAX 10256
+#define QPNP_PON_S2_TIMER_MAX 2000
+#define QPNP_PON_S3_TIMER_SECS_MAX 128
+#define QPNP_PON_S3_DBC_DELAY_MASK 0x07
+#define QPNP_PON_RESET_TYPE_MAX 0xF
+#define PON_S1_COUNT_MAX 0xF
+#define QPNP_PON_MIN_DBC_US (USEC_PER_SEC / 64)
+#define QPNP_PON_MAX_DBC_US (USEC_PER_SEC * 2)
+#define QPNP_PON_GEN2_MIN_DBC_US 62
+#define QPNP_PON_GEN2_MAX_DBC_US (USEC_PER_SEC / 4)
+
+#define QPNP_KEY_STATUS_DELAY msecs_to_jiffies(250)
+
+#define QPNP_PON_BUFFER_SIZE 9
+
+#define QPNP_POFF_REASON_UVLO 13
+
+enum qpnp_pon_version {
+ QPNP_PON_GEN1_V1,
+ QPNP_PON_GEN1_V2,
+ QPNP_PON_GEN2,
+};
+
+enum pon_type {
+ PON_KPDPWR,
+ PON_RESIN,
+ PON_CBLPWR,
+ PON_KPDPWR_RESIN,
+};
+
+struct qpnp_pon_config {
+ u32 pon_type;
+ u32 support_reset;
+ u32 key_code;
+ u32 s1_timer;
+ u32 s2_timer;
+ u32 s2_type;
+ u32 pull_up;
+ u32 state_irq;
+ u32 bark_irq;
+ u16 s2_cntl_addr;
+ u16 s2_cntl2_addr;
+ bool old_state;
+ bool use_bark;
+ bool config_reset;
+};
+
+struct pon_regulator {
+ struct qpnp_pon *pon;
+ struct regulator_dev *rdev;
+ struct regulator_desc rdesc;
+ u32 addr;
+ u32 bit;
+ bool enabled;
+};
+
+struct qpnp_pon {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct input_dev *pon_input;
+ struct qpnp_pon_config *pon_cfg;
+ struct pon_regulator *pon_reg_cfg;
+ struct list_head list;
+ struct delayed_work bark_work;
+ struct dentry *debugfs;
+ int pon_trigger_reason;
+ int pon_power_off_reason;
+ int num_pon_reg;
+ int num_pon_config;
+ u32 dbc;
+ u32 uvlo;
+ int warm_reset_poff_type;
+ int hard_reset_poff_type;
+ int shutdown_poff_type;
+ u16 base;
+ u8 subtype;
+ u8 pon_ver;
+ u8 warm_reset_reason1;
+ u8 warm_reset_reason2;
+ bool is_spon;
+ bool store_hard_reset_reason;
+};
+
+static int pon_ship_mode_en;
+module_param_named(
+ ship_mode_en, pon_ship_mode_en, int, 0600
+);
+
+static struct qpnp_pon *sys_reset_dev;
+static DEFINE_SPINLOCK(spon_list_slock);
+static LIST_HEAD(spon_dev_list);
+
+static u32 s1_delay[PON_S1_COUNT_MAX + 1] = {
+ 0, 32, 56, 80, 138, 184, 272, 408, 608, 904, 1352, 2048,
+ 3072, 4480, 6720, 10256
+};
+
+static const char * const qpnp_pon_reason[] = {
+ [0] = "Triggered from Hard Reset",
+ [1] = "Triggered from SMPL (sudden momentary power loss)",
+ [2] = "Triggered from RTC (RTC alarm expiry)",
+ [3] = "Triggered from DC (DC charger insertion)",
+ [4] = "Triggered from USB (USB charger insertion)",
+ [5] = "Triggered from PON1 (secondary PMIC)",
+ [6] = "Triggered from CBL (external power supply)",
+ [7] = "Triggered from KPD (power key press)",
+};
+
+#define POFF_REASON_FAULT_OFFSET 16
+#define POFF_REASON_S3_RESET_OFFSET 32
+static const char * const qpnp_poff_reason[] = {
+ /* QPNP_PON_GEN1 POFF reasons */
+ [0] = "Triggered from SOFT (Software)",
+ [1] = "Triggered from PS_HOLD (PS_HOLD/MSM controlled shutdown)",
+ [2] = "Triggered from PMIC_WD (PMIC watchdog)",
+ [3] = "Triggered from GP1 (Keypad_Reset1)",
+ [4] = "Triggered from GP2 (Keypad_Reset2)",
+ [5] = "Triggered from KPDPWR_AND_RESIN (Simultaneous power key and reset line)",
+ [6] = "Triggered from RESIN_N (Reset line/Volume Down Key)",
+ [7] = "Triggered from KPDPWR_N (Long Power Key hold)",
+ [8] = "N/A",
+ [9] = "N/A",
+ [10] = "N/A",
+ [11] = "Triggered from CHARGER (Charger ENUM_TIMER, BOOT_DONE)",
+ [12] = "Triggered from TFT (Thermal Fault Tolerance)",
+ [13] = "Triggered from UVLO (Under Voltage Lock Out)",
+ [14] = "Triggered from OTST3 (Overtemp)",
+ [15] = "Triggered from STAGE3 (Stage 3 reset)",
+
+ /* QPNP_PON_GEN2 FAULT reasons */
+ [16] = "Triggered from GP_FAULT0",
+ [17] = "Triggered from GP_FAULT1",
+ [18] = "Triggered from GP_FAULT2",
+ [19] = "Triggered from GP_FAULT3",
+ [20] = "Triggered from MBG_FAULT",
+ [21] = "Triggered from OVLO (Over Voltage Lock Out)",
+ [22] = "Triggered from UVLO (Under Voltage Lock Out)",
+ [23] = "Triggered from AVDD_RB",
+ [24] = "N/A",
+ [25] = "N/A",
+ [26] = "N/A",
+ [27] = "Triggered from FAULT_FAULT_N",
+ [28] = "Triggered from FAULT_PBS_WATCHDOG_TO",
+ [29] = "Triggered from FAULT_PBS_NACK",
+ [30] = "Triggered from FAULT_RESTART_PON",
+ [31] = "Triggered from OTST3 (Overtemp)",
+
+ /* QPNP_PON_GEN2 S3_RESET reasons */
+ [32] = "N/A",
+ [33] = "N/A",
+ [34] = "N/A",
+ [35] = "N/A",
+ [36] = "Triggered from S3_RESET_FAULT_N",
+ [37] = "Triggered from S3_RESET_PBS_WATCHDOG_TO",
+ [38] = "Triggered from S3_RESET_PBS_NACK",
+ [39] = "Triggered from S3_RESET_KPDPWR_ANDOR_RESIN (power key and/or reset line)",
+};
+
+static int
+qpnp_pon_masked_write(struct qpnp_pon *pon, u16 addr, u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(pon->regmap, addr, mask, val);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to regmap_update_bits to addr=%hx, rc(%d)\n",
+ addr, rc);
+ return rc;
+}
+
+static bool is_pon_gen1(struct qpnp_pon *pon)
+{
+ return pon->subtype == PON_PRIMARY ||
+ pon->subtype == PON_SECONDARY;
+}
+
+static bool is_pon_gen2(struct qpnp_pon *pon)
+{
+ return pon->subtype == PON_GEN2_PRIMARY ||
+ pon->subtype == PON_GEN2_SECONDARY;
+}
+
+/**
+ * qpnp_pon_set_restart_reason - Store device restart reason in PMIC register.
+ *
+ * Returns = 0 if PMIC feature is not available or store restart reason
+ * successfully.
+ * Returns > 0 for errors
+ *
+ * This function is used to store device restart reason in PMIC register.
+ * It checks here to see if the restart reason register has been specified.
+ * If it hasn't, this function should immediately return 0
+ */
+int qpnp_pon_set_restart_reason(enum pon_restart_reason reason)
+{
+ int rc = 0;
+ struct qpnp_pon *pon = sys_reset_dev;
+
+ if (!pon)
+ return 0;
+
+ if (!pon->store_hard_reset_reason)
+ return 0;
+
+ if (is_pon_gen2(pon))
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_SOFT_RB_SPARE(pon),
+ GENMASK(7, 1), (reason << 1));
+ else
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_SOFT_RB_SPARE(pon),
+ GENMASK(7, 2), (reason << 2));
+
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ QPNP_PON_SOFT_RB_SPARE(pon), rc);
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pon_set_restart_reason);
+
+/*
+ * qpnp_pon_check_hard_reset_stored - Checks if the PMIC need to
+ * store hard reset reason.
+ *
+ * Returns true if reset reason can be stored, false if it cannot be stored
+ *
+ */
+bool qpnp_pon_check_hard_reset_stored(void)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+
+ if (!pon)
+ return false;
+
+ return pon->store_hard_reset_reason;
+}
+EXPORT_SYMBOL(qpnp_pon_check_hard_reset_stored);
+
+static int qpnp_pon_set_dbc(struct qpnp_pon *pon, u32 delay)
+{
+ int rc = 0;
+ u32 val;
+
+ if (delay == pon->dbc)
+ goto out;
+
+ if (pon->pon_input)
+ mutex_lock(&pon->pon_input->mutex);
+
+ if (is_pon_gen2(pon)) {
+ if (delay < QPNP_PON_GEN2_MIN_DBC_US)
+ delay = QPNP_PON_GEN2_MIN_DBC_US;
+ else if (delay > QPNP_PON_GEN2_MAX_DBC_US)
+ delay = QPNP_PON_GEN2_MAX_DBC_US;
+ val = (delay << QPNP_PON_GEN2_DELAY_BIT_SHIFT) / USEC_PER_SEC;
+ } else {
+ if (delay < QPNP_PON_MIN_DBC_US)
+ delay = QPNP_PON_MIN_DBC_US;
+ else if (delay > QPNP_PON_MAX_DBC_US)
+ delay = QPNP_PON_MAX_DBC_US;
+ val = (delay << QPNP_PON_DELAY_BIT_SHIFT) / USEC_PER_SEC;
+ }
+
+ val = ilog2(val);
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_DBC_CTL(pon),
+ QPNP_PON_DBC_DELAY_MASK(pon), val);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to set PON debounce\n");
+ goto unlock;
+ }
+
+ pon->dbc = delay;
+
+unlock:
+ if (pon->pon_input)
+ mutex_unlock(&pon->pon_input->mutex);
+out:
+ return rc;
+}
+
+static ssize_t qpnp_pon_dbc_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_pon *pon = dev_get_drvdata(dev);
+
+ return snprintf(buf, QPNP_PON_BUFFER_SIZE, "%d\n", pon->dbc);
+}
+
+static ssize_t qpnp_pon_dbc_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qpnp_pon *pon = dev_get_drvdata(dev);
+ u32 value;
+ int rc;
+
+ if (size > QPNP_PON_BUFFER_SIZE)
+ return -EINVAL;
+
+ rc = kstrtou32(buf, 10, &value);
+ if (rc)
+ return rc;
+
+ rc = qpnp_pon_set_dbc(pon, value);
+ if (rc < 0)
+ return rc;
+
+ return size;
+}
+
+static DEVICE_ATTR(debounce_us, 0664, qpnp_pon_dbc_show, qpnp_pon_dbc_store);
+
+static int qpnp_pon_reset_config(struct qpnp_pon *pon,
+ enum pon_power_off_type type)
+{
+ int rc;
+ u16 rst_en_reg;
+
+ if (pon->pon_ver == QPNP_PON_GEN1_V1)
+ rst_en_reg = QPNP_PON_PS_HOLD_RST_CTL(pon);
+ else
+ rst_en_reg = QPNP_PON_PS_HOLD_RST_CTL2(pon);
+
+ /*
+ * Based on the poweroff type set for a PON device through device tree
+ * change the type being configured into PS_HOLD_RST_CTL.
+ */
+ switch (type) {
+ case PON_POWER_OFF_WARM_RESET:
+ if (pon->warm_reset_poff_type != -EINVAL)
+ type = pon->warm_reset_poff_type;
+ break;
+ case PON_POWER_OFF_HARD_RESET:
+ if (pon->hard_reset_poff_type != -EINVAL)
+ type = pon->hard_reset_poff_type;
+ break;
+ case PON_POWER_OFF_SHUTDOWN:
+ if (pon->shutdown_poff_type != -EINVAL)
+ type = pon->shutdown_poff_type;
+ break;
+ default:
+ break;
+ }
+
+ rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN, 0);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%hx, rc(%d)\n",
+ rst_en_reg, rc);
+
+ /*
+ * We need 10 sleep clock cycles here. But since the clock is
+ * internally generated, we need to add 50% tolerance to be
+ * conservative.
+ */
+ udelay(500);
+
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_PS_HOLD_RST_CTL(pon),
+ QPNP_PON_POWER_OFF_MASK, type);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ QPNP_PON_PS_HOLD_RST_CTL(pon), rc);
+
+ rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN,
+ QPNP_PON_RESET_EN);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%hx, rc(%d)\n",
+ rst_en_reg, rc);
+
+ dev_dbg(&pon->pdev->dev, "power off type = 0x%02X\n", type);
+ return rc;
+}
+
+/**
+ * qpnp_pon_system_pwr_off - Configure system-reset PMIC for shutdown or reset
+ * @type: Determines the type of power off to perform - shutdown, reset, etc
+ *
+ * This function will support configuring for multiple PMICs. In some cases, the
+ * PON of secondary PMICs also needs to be configured. So this supports that
+ * requirement. Once the system-reset and secondary PMIC is configured properly,
+ * the MSM can drop PS_HOLD to activate the specified configuration. Note that
+ * this function may be called from atomic context as in the case of the panic
+ * notifier path and thus it should not rely on function calls that may sleep.
+ */
+int qpnp_pon_system_pwr_off(enum pon_power_off_type type)
+{
+ int rc = 0;
+ struct qpnp_pon *pon = sys_reset_dev;
+ struct qpnp_pon *tmp;
+ struct power_supply *batt_psy;
+ union power_supply_propval val;
+ unsigned long flags;
+
+ if (!pon)
+ return -ENODEV;
+
+ rc = qpnp_pon_reset_config(pon, type);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Error configuring main PON rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ /*
+ * Check if a secondary PON device needs to be configured. If it
+ * is available, configure that also as per the requested power off
+ * type
+ */
+ spin_lock_irqsave(&spon_list_slock, flags);
+ if (list_empty(&spon_dev_list))
+ goto out;
+
+ list_for_each_entry_safe(pon, tmp, &spon_dev_list, list) {
+ dev_emerg(&pon->pdev->dev,
+ "PMIC@SID%d: configuring PON for reset\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid);
+ rc = qpnp_pon_reset_config(pon, type);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Error configuring secondary PON rc: %d\n",
+ rc);
+ goto out;
+ }
+ }
+ /* Set ship mode here if it has been requested */
+ if (!!pon_ship_mode_en) {
+ batt_psy = power_supply_get_by_name("battery");
+ if (batt_psy) {
+ pr_debug("Set ship mode!\n");
+ val.intval = 1;
+ rc = power_supply_set_property(batt_psy,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE, &val);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Set ship-mode failed\n");
+ }
+ }
+out:
+ spin_unlock_irqrestore(&spon_list_slock, flags);
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pon_system_pwr_off);
+
+/**
+ * qpnp_pon_is_warm_reset - Checks if the PMIC went through a warm reset.
+ *
+ * Returns > 0 for warm resets, 0 for not warm reset, < 0 for errors
+ *
+ * Note that this function will only return the warm vs not-warm reset status
+ * of the PMIC that is configured as the system-reset device.
+ */
+int qpnp_pon_is_warm_reset(void)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+
+ if (!pon)
+ return -EPROBE_DEFER;
+
+ if (is_pon_gen1(pon) || pon->subtype == PON_1REG)
+ return pon->warm_reset_reason1
+ || (pon->warm_reset_reason2 & QPNP_PON_WARM_RESET_TFT);
+ else
+ return pon->warm_reset_reason1;
+}
+EXPORT_SYMBOL(qpnp_pon_is_warm_reset);
+
+/**
+ * qpnp_pon_wd_config - Disable the wd in a warm reset.
+ * @enable: to enable or disable the PON watch dog
+ *
+ * Returns = 0 for operate successfully, < 0 for errors
+ */
+int qpnp_pon_wd_config(bool enable)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+ int rc = 0;
+
+ if (!pon)
+ return -EPROBE_DEFER;
+
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_WD_RST_S2_CTL2(pon),
+ QPNP_PON_WD_EN, enable ? QPNP_PON_WD_EN : 0);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ QPNP_PON_WD_RST_S2_CTL2(pon), rc);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pon_wd_config);
+
+static int qpnp_pon_get_trigger_config(enum pon_trigger_source pon_src,
+ bool *enabled)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+ int rc;
+ u16 addr;
+ int val;
+ u8 mask;
+
+ if (!pon)
+ return -ENODEV;
+
+ if (pon_src < PON_SMPL || pon_src > PON_KPDPWR_N) {
+ dev_err(&pon->pdev->dev, "Invalid PON source\n");
+ return -EINVAL;
+ }
+
+ addr = QPNP_PON_TRIGGER_EN(pon);
+ mask = BIT(pon_src);
+ if (is_pon_gen2(pon) && pon_src == PON_SMPL) {
+ addr = QPNP_PON_SMPL_CTL(pon);
+ mask = QPNP_PON_SMPL_EN;
+ }
+
+
+ rc = regmap_read(pon->regmap, addr, &val);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to read from addr=%hx, rc(%d)\n",
+ addr, rc);
+ else
+ *enabled = !!(val & mask);
+
+ return rc;
+}
+
+/**
+ * qpnp_pon_trigger_config - Configures (enable/disable) the PON trigger source
+ * @pon_src: PON source to be configured
+ * @enable: to enable or disable the PON trigger
+ *
+ * This function configures the power-on trigger capability of a
+ * PON source. If a specific PON trigger is disabled it cannot act
+ * as a power-on source to the PMIC.
+ */
+
+int qpnp_pon_trigger_config(enum pon_trigger_source pon_src, bool enable)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+ int rc;
+
+ if (!pon)
+ return -EPROBE_DEFER;
+
+ if (pon_src < PON_SMPL || pon_src > PON_KPDPWR_N) {
+ dev_err(&pon->pdev->dev, "Invalid PON source\n");
+ return -EINVAL;
+ }
+
+ if (is_pon_gen2(pon) && pon_src == PON_SMPL) {
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_SMPL_CTL(pon),
+ QPNP_PON_SMPL_EN, enable ? QPNP_PON_SMPL_EN : 0);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ QPNP_PON_SMPL_CTL(pon), rc);
+ } else {
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_TRIGGER_EN(pon),
+ BIT(pon_src), enable ? BIT(pon_src) : 0);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%x, rc(%d)\n",
+ QPNP_PON_TRIGGER_EN(pon), rc);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pon_trigger_config);
+
+/*
+ * This function stores the PMIC warm reset reason register values. It also
+ * clears these registers if the qcom,clear-warm-reset device tree property
+ * is specified.
+ */
+static int qpnp_pon_store_and_clear_warm_reset(struct qpnp_pon *pon)
+{
+ int rc;
+ u8 reg = 0;
+ uint val;
+
+ rc = regmap_read(pon->regmap, QPNP_PON_WARM_RESET_REASON1(pon),
+ &val);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read addr=%x, rc(%d)\n",
+ QPNP_PON_WARM_RESET_REASON1(pon), rc);
+ return rc;
+ }
+ pon->warm_reset_reason1 = (u8)val;
+
+ if (is_pon_gen1(pon) || pon->subtype == PON_1REG) {
+ rc = regmap_read(pon->regmap, QPNP_PON_WARM_RESET_REASON2(pon),
+ &val);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read addr=%x, rc(%d)\n",
+ QPNP_PON_WARM_RESET_REASON2(pon), rc);
+ return rc;
+ }
+ pon->warm_reset_reason2 = (u8)val;
+ }
+
+ if (of_property_read_bool(pon->pdev->dev.of_node,
+ "qcom,clear-warm-reset")) {
+ rc = regmap_write(pon->regmap,
+ QPNP_PON_WARM_RESET_REASON1(pon), reg);
+ if (rc)
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%hx, rc(%d)\n",
+ QPNP_PON_WARM_RESET_REASON1(pon), rc);
+ }
+
+ return 0;
+}
+
+static struct qpnp_pon_config *
+qpnp_get_cfg(struct qpnp_pon *pon, u32 pon_type)
+{
+ int i;
+
+ for (i = 0; i < pon->num_pon_config; i++) {
+ if (pon_type == pon->pon_cfg[i].pon_type)
+ return &pon->pon_cfg[i];
+ }
+
+ return NULL;
+}
+
+static int
+qpnp_pon_input_dispatch(struct qpnp_pon *pon, u32 pon_type)
+{
+ int rc;
+ struct qpnp_pon_config *cfg = NULL;
+ u8 pon_rt_bit = 0;
+ u32 key_status;
+ uint pon_rt_sts;
+
+ cfg = qpnp_get_cfg(pon, pon_type);
+ if (!cfg)
+ return -EINVAL;
+
+ /* Check if key reporting is supported */
+ if (!cfg->key_code)
+ return 0;
+
+ /* check the RT status to get the current status of the line */
+ rc = regmap_read(pon->regmap, QPNP_PON_RT_STS(pon), &pon_rt_sts);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read PON RT status\n");
+ return rc;
+ }
+
+ switch (cfg->pon_type) {
+ case PON_KPDPWR:
+ pon_rt_bit = QPNP_PON_KPDPWR_N_SET;
+ break;
+ case PON_RESIN:
+ pon_rt_bit = QPNP_PON_RESIN_N_SET;
+ break;
+ case PON_CBLPWR:
+ pon_rt_bit = QPNP_PON_CBLPWR_N_SET;
+ break;
+ case PON_KPDPWR_RESIN:
+ pon_rt_bit = QPNP_PON_KPDPWR_RESIN_BARK_N_SET;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pr_debug("PMIC input: code=%d, sts=0x%hhx\n",
+ cfg->key_code, pon_rt_sts);
+ key_status = pon_rt_sts & pon_rt_bit;
+
+ /*
+ * simulate press event in case release event occurred
+ * without a press event
+ */
+ if (!cfg->old_state && !key_status) {
+ input_report_key(pon->pon_input, cfg->key_code, 1);
+ input_sync(pon->pon_input);
+ }
+
+ input_report_key(pon->pon_input, cfg->key_code, key_status);
+ input_sync(pon->pon_input);
+
+ cfg->old_state = !!key_status;
+
+ return 0;
+}
+
+static irqreturn_t qpnp_kpdpwr_irq(int irq, void *_pon)
+{
+ int rc;
+ struct qpnp_pon *pon = _pon;
+
+ rc = qpnp_pon_input_dispatch(pon, PON_KPDPWR);
+ if (rc)
+ dev_err(&pon->pdev->dev, "Unable to send input event\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qpnp_kpdpwr_bark_irq(int irq, void *_pon)
+{
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qpnp_resin_irq(int irq, void *_pon)
+{
+ int rc;
+ struct qpnp_pon *pon = _pon;
+
+ rc = qpnp_pon_input_dispatch(pon, PON_RESIN);
+ if (rc)
+ dev_err(&pon->pdev->dev, "Unable to send input event\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qpnp_kpdpwr_resin_bark_irq(int irq, void *_pon)
+{
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qpnp_cblpwr_irq(int irq, void *_pon)
+{
+ int rc;
+ struct qpnp_pon *pon = _pon;
+
+ rc = qpnp_pon_input_dispatch(pon, PON_CBLPWR);
+ if (rc)
+ dev_err(&pon->pdev->dev, "Unable to send input event\n");
+
+ return IRQ_HANDLED;
+}
+
+static void print_pon_reg(struct qpnp_pon *pon, u16 offset)
+{
+ int rc;
+ u16 addr;
+ uint reg;
+
+ addr = pon->base + offset;
+ rc = regmap_read(pon->regmap, addr, ®);
+ if (rc)
+ dev_emerg(&pon->pdev->dev,
+ "Unable to read reg at 0x%04hx\n", addr);
+ else
+ dev_emerg(&pon->pdev->dev, "reg@0x%04hx: %02hhx\n", addr, reg);
+}
+
+#define PON_PBL_STATUS 0x7
+#define PON_PON_REASON1(subtype) PON_OFFSET(subtype, 0x8, 0xC0)
+#define PON_PON_REASON2 0x9
+#define PON_WARM_RESET_REASON1(subtype) PON_OFFSET(subtype, 0xA, 0xC2)
+#define PON_WARM_RESET_REASON2 0xB
+#define PON_POFF_REASON1(subtype) PON_OFFSET(subtype, 0xC, 0xC5)
+#define PON_POFF_REASON2 0xD
+#define PON_SOFT_RESET_REASON1(subtype) PON_OFFSET(subtype, 0xE, 0xCB)
+#define PON_SOFT_RESET_REASON2 0xF
+#define PON_FAULT_REASON1 0xC8
+#define PON_FAULT_REASON2 0xC9
+#define PON_PMIC_WD_RESET_S1_TIMER 0x54
+#define PON_PMIC_WD_RESET_S2_TIMER 0x55
+static irqreturn_t qpnp_pmic_wd_bark_irq(int irq, void *_pon)
+{
+ struct qpnp_pon *pon = _pon;
+
+ print_pon_reg(pon, PON_PBL_STATUS);
+ print_pon_reg(pon, PON_PON_REASON1(pon->subtype));
+ print_pon_reg(pon, PON_WARM_RESET_REASON1(pon->subtype));
+ print_pon_reg(pon, PON_SOFT_RESET_REASON1(pon->subtype));
+ print_pon_reg(pon, PON_POFF_REASON1(pon->subtype));
+ if (is_pon_gen1(pon) || pon->subtype == PON_1REG) {
+ print_pon_reg(pon, PON_PON_REASON2);
+ print_pon_reg(pon, PON_WARM_RESET_REASON2);
+ print_pon_reg(pon, PON_POFF_REASON2);
+ print_pon_reg(pon, PON_SOFT_RESET_REASON2);
+ } else {
+ print_pon_reg(pon, PON_FAULT_REASON1);
+ print_pon_reg(pon, PON_FAULT_REASON2);
+ }
+ print_pon_reg(pon, PON_PMIC_WD_RESET_S1_TIMER);
+ print_pon_reg(pon, PON_PMIC_WD_RESET_S2_TIMER);
+ panic("PMIC Watch dog triggered");
+
+ return IRQ_HANDLED;
+}
+
+static void bark_work_func(struct work_struct *work)
+{
+ int rc;
+ uint pon_rt_sts = 0;
+ struct qpnp_pon_config *cfg;
+ struct qpnp_pon *pon =
+ container_of(work, struct qpnp_pon, bark_work.work);
+
+ cfg = qpnp_get_cfg(pon, PON_RESIN);
+ if (!cfg) {
+ dev_err(&pon->pdev->dev, "Invalid config pointer\n");
+ goto err_return;
+ }
+
+ /* enable reset */
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n");
+ goto err_return;
+ }
+ /* bark RT status update delay */
+ msleep(100);
+ /* read the bark RT status */
+ rc = regmap_read(pon->regmap, QPNP_PON_RT_STS(pon), &pon_rt_sts);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read PON RT status\n");
+ goto err_return;
+ }
+
+ if (!(pon_rt_sts & QPNP_PON_RESIN_BARK_N_SET)) {
+ /* report the key event and enable the bark IRQ */
+ input_report_key(pon->pon_input, cfg->key_code, 0);
+ input_sync(pon->pon_input);
+ enable_irq(cfg->bark_irq);
+ } else {
+ /* disable reset */
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, 0);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to configure S2 enable\n");
+ goto err_return;
+ }
+ /* re-arm the work */
+ schedule_delayed_work(&pon->bark_work, QPNP_KEY_STATUS_DELAY);
+ }
+
+err_return:
+ return;
+}
+
+static irqreturn_t qpnp_resin_bark_irq(int irq, void *_pon)
+{
+ int rc;
+ struct qpnp_pon *pon = _pon;
+ struct qpnp_pon_config *cfg;
+
+ /* disable the bark interrupt */
+ disable_irq_nosync(irq);
+
+ cfg = qpnp_get_cfg(pon, PON_RESIN);
+ if (!cfg) {
+ dev_err(&pon->pdev->dev, "Invalid config pointer\n");
+ goto err_exit;
+ }
+
+ /* disable reset */
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, 0);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n");
+ goto err_exit;
+ }
+
+ /* report the key event */
+ input_report_key(pon->pon_input, cfg->key_code, 1);
+ input_sync(pon->pon_input);
+ /* schedule work to check the bark status for key-release */
+ schedule_delayed_work(&pon->bark_work, QPNP_KEY_STATUS_DELAY);
+err_exit:
+ return IRQ_HANDLED;
+}
+
+static int
+qpnp_config_pull(struct qpnp_pon *pon, struct qpnp_pon_config *cfg)
+{
+ int rc;
+ u8 pull_bit;
+
+ switch (cfg->pon_type) {
+ case PON_KPDPWR:
+ pull_bit = QPNP_PON_KPDPWR_PULL_UP;
+ break;
+ case PON_RESIN:
+ pull_bit = QPNP_PON_RESIN_PULL_UP;
+ break;
+ case PON_CBLPWR:
+ pull_bit = QPNP_PON_CBLPWR_PULL_UP;
+ break;
+ case PON_KPDPWR_RESIN:
+ pull_bit = QPNP_PON_KPDPWR_PULL_UP | QPNP_PON_RESIN_PULL_UP;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_PULL_CTL(pon),
+ pull_bit, cfg->pull_up ? pull_bit : 0);
+ if (rc)
+ dev_err(&pon->pdev->dev, "Unable to config pull-up\n");
+
+ return rc;
+}
+
+static int
+qpnp_config_reset(struct qpnp_pon *pon, struct qpnp_pon_config *cfg)
+{
+ int rc;
+ u8 i;
+ u16 s1_timer_addr, s2_timer_addr;
+
+ switch (cfg->pon_type) {
+ case PON_KPDPWR:
+ s1_timer_addr = QPNP_PON_KPDPWR_S1_TIMER(pon);
+ s2_timer_addr = QPNP_PON_KPDPWR_S2_TIMER(pon);
+ break;
+ case PON_RESIN:
+ s1_timer_addr = QPNP_PON_RESIN_S1_TIMER(pon);
+ s2_timer_addr = QPNP_PON_RESIN_S2_TIMER(pon);
+ break;
+ case PON_KPDPWR_RESIN:
+ s1_timer_addr = QPNP_PON_KPDPWR_RESIN_S1_TIMER(pon);
+ s2_timer_addr = QPNP_PON_KPDPWR_RESIN_S2_TIMER(pon);
+ break;
+ default:
+ return -EINVAL;
+ }
+ /* disable S2 reset */
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, 0);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n");
+ return rc;
+ }
+
+ usleep_range(100, 120);
+
+ /* configure s1 timer, s2 timer and reset type */
+ for (i = 0; i < PON_S1_COUNT_MAX + 1; i++) {
+ if (cfg->s1_timer <= s1_delay[i])
+ break;
+ }
+ rc = qpnp_pon_masked_write(pon, s1_timer_addr,
+ QPNP_PON_S1_TIMER_MASK, i);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S1 timer\n");
+ return rc;
+ }
+
+ i = 0;
+ if (cfg->s2_timer) {
+ i = cfg->s2_timer / 10;
+ i = ilog2(i + 1);
+ }
+
+ rc = qpnp_pon_masked_write(pon, s2_timer_addr,
+ QPNP_PON_S2_TIMER_MASK, i);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S2 timer\n");
+ return rc;
+ }
+
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl_addr,
+ QPNP_PON_S2_CNTL_TYPE_MASK, (u8)cfg->s2_type);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to configure S2 reset type\n");
+ return rc;
+ }
+
+ /* enable S2 reset */
+ rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+qpnp_pon_request_irqs(struct qpnp_pon *pon, struct qpnp_pon_config *cfg)
+{
+ int rc = 0;
+
+ switch (cfg->pon_type) {
+ case PON_KPDPWR:
+ rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq,
+ qpnp_kpdpwr_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "qpnp_kpdpwr_status", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev, "Can't request %d IRQ\n",
+ cfg->state_irq);
+ return rc;
+ }
+ if (cfg->use_bark) {
+ rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq,
+ qpnp_kpdpwr_bark_irq,
+ IRQF_TRIGGER_RISING,
+ "qpnp_kpdpwr_bark", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev,
+ "Can't request %d IRQ\n",
+ cfg->bark_irq);
+ return rc;
+ }
+ }
+ break;
+ case PON_RESIN:
+ rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq,
+ qpnp_resin_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "qpnp_resin_status", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev, "Can't request %d IRQ\n",
+ cfg->state_irq);
+ return rc;
+ }
+ if (cfg->use_bark) {
+ rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq,
+ qpnp_resin_bark_irq,
+ IRQF_TRIGGER_RISING,
+ "qpnp_resin_bark", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev,
+ "Can't request %d IRQ\n",
+ cfg->bark_irq);
+ return rc;
+ }
+ }
+ break;
+ case PON_CBLPWR:
+ rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq,
+ qpnp_cblpwr_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "qpnp_cblpwr_status", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev, "Can't request %d IRQ\n",
+ cfg->state_irq);
+ return rc;
+ }
+ break;
+ case PON_KPDPWR_RESIN:
+ if (cfg->use_bark) {
+ rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq,
+ qpnp_kpdpwr_resin_bark_irq,
+ IRQF_TRIGGER_RISING,
+ "qpnp_kpdpwr_resin_bark", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev,
+ "Can't request %d IRQ\n",
+ cfg->bark_irq);
+ return rc;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* mark the interrupts wakeable if they support linux-key */
+ if (cfg->key_code) {
+ enable_irq_wake(cfg->state_irq);
+ /* special handling for RESIN due to a hardware bug */
+ if (cfg->pon_type == PON_RESIN && cfg->support_reset)
+ enable_irq_wake(cfg->bark_irq);
+ }
+
+ return rc;
+}
+
+static int
+qpnp_pon_config_input(struct qpnp_pon *pon, struct qpnp_pon_config *cfg)
+{
+ if (!pon->pon_input) {
+ pon->pon_input = input_allocate_device();
+ if (!pon->pon_input) {
+ dev_err(&pon->pdev->dev,
+ "Can't allocate pon input device\n");
+ return -ENOMEM;
+ }
+ pon->pon_input->name = "qpnp_pon";
+ pon->pon_input->phys = "qpnp_pon/input0";
+ }
+
+ input_set_capability(pon->pon_input, EV_KEY, cfg->key_code);
+
+ return 0;
+}
+
+static int qpnp_pon_config_init(struct qpnp_pon *pon)
+{
+ int rc = 0, i = 0, pmic_wd_bark_irq;
+ struct device_node *pp = NULL;
+ struct qpnp_pon_config *cfg;
+ uint pmic_type;
+ uint revid_rev4;
+
+ if (!pon->num_pon_config) {
+ dev_dbg(&pon->pdev->dev, "num_pon_config: %d\n",
+ pon->num_pon_config);
+ return 0;
+ }
+
+ /* iterate through the list of pon configs */
+ for_each_available_child_of_node(pon->pdev->dev.of_node, pp) {
+ if (!of_find_property(pp, "qcom,pon-type", NULL))
+ continue;
+
+ cfg = &pon->pon_cfg[i++];
+
+ rc = of_property_read_u32(pp, "qcom,pon-type", &cfg->pon_type);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "PON type not specified\n");
+ return rc;
+ }
+
+ switch (cfg->pon_type) {
+ case PON_KPDPWR:
+ cfg->state_irq = platform_get_irq_byname(pon->pdev,
+ "kpdpwr");
+ if (cfg->state_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get kpdpwr irq\n");
+ return cfg->state_irq;
+ }
+
+ rc = of_property_read_u32(pp, "qcom,support-reset",
+ &cfg->support_reset);
+
+ if (rc) {
+ if (rc == -EINVAL) {
+ dev_dbg(&pon->pdev->dev,
+ "'qcom,support-reset' DT property doesn't exist\n");
+ } else {
+ dev_err(&pon->pdev->dev,
+ "Unable to read 'qcom,support-reset'\n");
+ return rc;
+ }
+ } else {
+ cfg->config_reset = true;
+ }
+
+ cfg->use_bark = of_property_read_bool(pp,
+ "qcom,use-bark");
+ if (cfg->use_bark) {
+ cfg->bark_irq
+ = platform_get_irq_byname(pon->pdev,
+ "kpdpwr-bark");
+ if (cfg->bark_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get kpdpwr-bark irq\n");
+ return cfg->bark_irq;
+ }
+ }
+
+ /*
+ * If the value read from REVISION2 register is 0x00,
+ * then there is a single register to control s2 reset.
+ * Otherwise there are separate registers for s2 reset
+ * type and s2 reset enable.
+ */
+ if (pon->pon_ver == QPNP_PON_GEN1_V1) {
+ cfg->s2_cntl_addr = cfg->s2_cntl2_addr =
+ QPNP_PON_KPDPWR_S2_CNTL(pon);
+ } else {
+ cfg->s2_cntl_addr =
+ QPNP_PON_KPDPWR_S2_CNTL(pon);
+ cfg->s2_cntl2_addr =
+ QPNP_PON_KPDPWR_S2_CNTL2(pon);
+ }
+
+ break;
+ case PON_RESIN:
+ cfg->state_irq = platform_get_irq_byname(pon->pdev,
+ "resin");
+ if (cfg->state_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get resin irq\n");
+ return cfg->bark_irq;
+ }
+
+ rc = of_property_read_u32(pp, "qcom,support-reset",
+ &cfg->support_reset);
+
+ if (rc) {
+ if (rc == -EINVAL) {
+ dev_dbg(&pon->pdev->dev,
+ "'qcom,support-reset' DT property doesn't exist\n");
+ } else {
+ dev_err(&pon->pdev->dev,
+ "Unable to read 'qcom,support-reset'\n");
+ return rc;
+ }
+ } else {
+ cfg->config_reset = true;
+ }
+
+ cfg->use_bark = of_property_read_bool(pp,
+ "qcom,use-bark");
+
+ rc = regmap_read(pon->regmap, PMIC_VERSION_REG,
+ &pmic_type);
+
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read PMIC type\n");
+ return rc;
+ }
+
+ if (pmic_type == PMIC_VER_8941) {
+
+ rc = regmap_read(pon->regmap,
+ PMIC_VERSION_REV4_REG,
+ &revid_rev4);
+
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read PMIC revision ID\n");
+ return rc;
+ }
+
+ /*
+ * PM8941 V3 does not have hardware bug. Hence
+ * bark is not required from PMIC versions 3.0.
+ */
+ if (!(revid_rev4 == PMIC8941_V1_REV4 ||
+ revid_rev4 == PMIC8941_V2_REV4)) {
+ cfg->support_reset = false;
+ cfg->use_bark = false;
+ }
+ }
+
+ if (cfg->use_bark) {
+ cfg->bark_irq
+ = platform_get_irq_byname(pon->pdev,
+ "resin-bark");
+ if (cfg->bark_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get resin-bark irq\n");
+ return cfg->bark_irq;
+ }
+ }
+
+ if (pon->pon_ver == QPNP_PON_GEN1_V1) {
+ cfg->s2_cntl_addr = cfg->s2_cntl2_addr =
+ QPNP_PON_RESIN_S2_CNTL(pon);
+ } else {
+ cfg->s2_cntl_addr =
+ QPNP_PON_RESIN_S2_CNTL(pon);
+ cfg->s2_cntl2_addr =
+ QPNP_PON_RESIN_S2_CNTL2(pon);
+ }
+
+ break;
+ case PON_CBLPWR:
+ cfg->state_irq = platform_get_irq_byname(pon->pdev,
+ "cblpwr");
+ if (cfg->state_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get cblpwr irq\n");
+ return rc;
+ }
+ break;
+ case PON_KPDPWR_RESIN:
+ rc = of_property_read_u32(pp, "qcom,support-reset",
+ &cfg->support_reset);
+
+ if (rc) {
+ if (rc == -EINVAL) {
+ dev_dbg(&pon->pdev->dev,
+ "'qcom,support-reset' DT property doesn't exist\n");
+ } else {
+ dev_err(&pon->pdev->dev,
+ "Unable to read 'qcom,support-reset'\n");
+ return rc;
+ }
+ } else {
+ cfg->config_reset = true;
+ }
+
+ cfg->use_bark = of_property_read_bool(pp,
+ "qcom,use-bark");
+ if (cfg->use_bark) {
+ cfg->bark_irq
+ = platform_get_irq_byname(pon->pdev,
+ "kpdpwr-resin-bark");
+ if (cfg->bark_irq < 0) {
+ dev_err(&pon->pdev->dev,
+ "Unable to get kpdpwr-resin-bark irq\n");
+ return cfg->bark_irq;
+ }
+ }
+
+ if (pon->pon_ver == QPNP_PON_GEN1_V1) {
+ cfg->s2_cntl_addr = cfg->s2_cntl2_addr =
+ QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon);
+ } else {
+ cfg->s2_cntl_addr =
+ QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon);
+ cfg->s2_cntl2_addr =
+ QPNP_PON_KPDPWR_RESIN_S2_CNTL2(pon);
+ }
+
+ break;
+ default:
+ dev_err(&pon->pdev->dev, "PON RESET %d not supported",
+ cfg->pon_type);
+ return -EINVAL;
+ }
+
+ if (cfg->support_reset) {
+ /*
+ * Get the reset parameters (bark debounce time and
+ * reset debounce time) for the reset line.
+ */
+ rc = of_property_read_u32(pp, "qcom,s1-timer",
+ &cfg->s1_timer);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read s1-timer\n");
+ return rc;
+ }
+ if (cfg->s1_timer > QPNP_PON_S1_TIMER_MAX) {
+ dev_err(&pon->pdev->dev,
+ "Incorrect S1 debounce time\n");
+ return -EINVAL;
+ }
+ rc = of_property_read_u32(pp, "qcom,s2-timer",
+ &cfg->s2_timer);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read s2-timer\n");
+ return rc;
+ }
+ if (cfg->s2_timer > QPNP_PON_S2_TIMER_MAX) {
+ dev_err(&pon->pdev->dev,
+ "Incorrect S2 debounce time\n");
+ return -EINVAL;
+ }
+ rc = of_property_read_u32(pp, "qcom,s2-type",
+ &cfg->s2_type);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read s2-type\n");
+ return rc;
+ }
+ if (cfg->s2_type > QPNP_PON_RESET_TYPE_MAX) {
+ dev_err(&pon->pdev->dev,
+ "Incorrect reset type specified\n");
+ return -EINVAL;
+ }
+ }
+ /*
+ * Get the standard-key parameters. This might not be
+ * specified if there is no key mapping on the reset line.
+ */
+ rc = of_property_read_u32(pp, "linux,code", &cfg->key_code);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pon->pdev->dev, "Unable to read key-code\n");
+ return rc;
+ }
+ /* Register key configuration */
+ if (cfg->key_code) {
+ rc = qpnp_pon_config_input(pon, cfg);
+ if (rc < 0)
+ return rc;
+ }
+ /* get the pull-up configuration */
+ rc = of_property_read_u32(pp, "qcom,pull-up", &cfg->pull_up);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pon->pdev->dev, "Unable to read pull-up\n");
+ return rc;
+ }
+ }
+
+ pmic_wd_bark_irq = platform_get_irq_byname(pon->pdev, "pmic-wd-bark");
+ /* request the pmic-wd-bark irq only if it is defined */
+ if (pmic_wd_bark_irq >= 0) {
+ rc = devm_request_irq(&pon->pdev->dev, pmic_wd_bark_irq,
+ qpnp_pmic_wd_bark_irq,
+ IRQF_TRIGGER_RISING,
+ "qpnp_pmic_wd_bark", pon);
+ if (rc < 0) {
+ dev_err(&pon->pdev->dev,
+ "Can't request %d IRQ\n",
+ pmic_wd_bark_irq);
+ goto free_input_dev;
+ }
+ }
+
+ /* register the input device */
+ if (pon->pon_input) {
+ rc = input_register_device(pon->pon_input);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Can't register pon key: %d\n", rc);
+ goto free_input_dev;
+ }
+ }
+
+ for (i = 0; i < pon->num_pon_config; i++) {
+ cfg = &pon->pon_cfg[i];
+ /* Configure the pull-up */
+ rc = qpnp_config_pull(pon, cfg);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to config pull-up\n");
+ goto unreg_input_dev;
+ }
+
+ if (cfg->config_reset) {
+ /* Configure the reset-configuration */
+ if (cfg->support_reset) {
+ rc = qpnp_config_reset(pon, cfg);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to config pon reset\n");
+ goto unreg_input_dev;
+ }
+ } else {
+ if (cfg->pon_type != PON_CBLPWR) {
+ /* disable S2 reset */
+ rc = qpnp_pon_masked_write(pon,
+ cfg->s2_cntl2_addr,
+ QPNP_PON_S2_CNTL_EN, 0);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to disable S2 reset\n");
+ goto unreg_input_dev;
+ }
+ }
+ }
+ }
+
+ rc = qpnp_pon_request_irqs(pon, cfg);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to request-irq's\n");
+ goto unreg_input_dev;
+ }
+ }
+
+ device_init_wakeup(&pon->pdev->dev, 1);
+
+ return rc;
+
+unreg_input_dev:
+ if (pon->pon_input)
+ input_unregister_device(pon->pon_input);
+free_input_dev:
+ if (pon->pon_input)
+ input_free_device(pon->pon_input);
+ return rc;
+}
+
+static int pon_spare_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 value;
+ struct pon_regulator *pon_reg = rdev_get_drvdata(rdev);
+
+ pr_debug("reg %s enable addr: %x bit: %d\n", rdev->desc->name,
+ pon_reg->addr, pon_reg->bit);
+
+ value = BIT(pon_reg->bit) & 0xFF;
+ rc = qpnp_pon_masked_write(pon_reg->pon, pon_reg->pon->base +
+ pon_reg->addr, value, value);
+ if (rc)
+ dev_err(&pon_reg->pon->pdev->dev, "Unable to write to %x\n",
+ pon_reg->pon->base + pon_reg->addr);
+ else
+ pon_reg->enabled = true;
+ return rc;
+}
+
+static int pon_spare_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 mask;
+ struct pon_regulator *pon_reg = rdev_get_drvdata(rdev);
+
+ pr_debug("reg %s disable addr: %x bit: %d\n", rdev->desc->name,
+ pon_reg->addr, pon_reg->bit);
+
+ mask = BIT(pon_reg->bit) & 0xFF;
+ rc = qpnp_pon_masked_write(pon_reg->pon, pon_reg->pon->base +
+ pon_reg->addr, mask, 0);
+ if (rc)
+ dev_err(&pon_reg->pon->pdev->dev, "Unable to write to %x\n",
+ pon_reg->pon->base + pon_reg->addr);
+ else
+ pon_reg->enabled = false;
+ return rc;
+}
+
+static int pon_spare_regulator_is_enable(struct regulator_dev *rdev)
+{
+ struct pon_regulator *pon_reg = rdev_get_drvdata(rdev);
+
+ return pon_reg->enabled;
+}
+
+struct regulator_ops pon_spare_reg_ops = {
+ .enable = pon_spare_regulator_enable,
+ .disable = pon_spare_regulator_disable,
+ .is_enabled = pon_spare_regulator_is_enable,
+};
+
+static int pon_regulator_init(struct qpnp_pon *pon)
+{
+ int rc = 0, i = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_config reg_cfg = {};
+ struct device_node *node = NULL;
+ struct device *dev = &pon->pdev->dev;
+ struct pon_regulator *pon_reg;
+
+ if (!pon->num_pon_reg)
+ return 0;
+
+ pon->pon_reg_cfg = devm_kcalloc(dev, pon->num_pon_reg,
+ sizeof(*(pon->pon_reg_cfg)),
+ GFP_KERNEL);
+
+ if (!pon->pon_reg_cfg)
+ return -ENOMEM;
+
+ for_each_available_child_of_node(dev->of_node, node) {
+ if (!of_find_property(node, "regulator-name", NULL))
+ continue;
+
+ pon_reg = &pon->pon_reg_cfg[i++];
+ pon_reg->pon = pon;
+
+ rc = of_property_read_u32(node, "qcom,pon-spare-reg-addr",
+ &pon_reg->addr);
+ if (rc) {
+ dev_err(dev, "Unable to read address for regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,pon-spare-reg-bit",
+ &pon_reg->bit);
+ if (rc) {
+ dev_err(dev, "Unable to read bit for regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ init_data = of_get_regulator_init_data(dev, node,
+ &pon_reg->rdesc);
+ if (!init_data) {
+ dev_err(dev, "regulator init data is missing\n");
+ return -EINVAL;
+ }
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_STATUS;
+
+ if (!init_data->constraints.name) {
+ dev_err(dev, "regulator-name is missing\n");
+ return -EINVAL;
+ }
+
+ pon_reg->rdesc.owner = THIS_MODULE;
+ pon_reg->rdesc.type = REGULATOR_VOLTAGE;
+ pon_reg->rdesc.ops = &pon_spare_reg_ops;
+ pon_reg->rdesc.name = init_data->constraints.name;
+
+ reg_cfg.dev = dev;
+ reg_cfg.init_data = init_data;
+ reg_cfg.driver_data = pon_reg;
+ reg_cfg.of_node = node;
+
+ pon_reg->rdev = regulator_register(&pon_reg->rdesc, ®_cfg);
+ if (IS_ERR(pon_reg->rdev)) {
+ rc = PTR_ERR(pon_reg->rdev);
+ pon_reg->rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ dev_err(dev, "regulator_register failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ return rc;
+}
+
+static bool smpl_en;
+
+static int qpnp_pon_smpl_en_get(char *buf, const struct kernel_param *kp)
+{
+ bool enabled = 0;
+ int rc;
+
+ rc = qpnp_pon_get_trigger_config(PON_SMPL, &enabled);
+ if (rc < 0)
+ return rc;
+
+ return snprintf(buf, QPNP_PON_BUFFER_SIZE, "%d", enabled);
+}
+
+static int qpnp_pon_smpl_en_set(const char *val,
+ const struct kernel_param *kp)
+{
+ int rc;
+
+ rc = param_set_bool(val, kp);
+ if (rc < 0) {
+ pr_err("Unable to set smpl_en rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_pon_trigger_config(PON_SMPL, *(bool *)kp->arg);
+ return rc;
+}
+
+static struct kernel_param_ops smpl_en_ops = {
+ .set = qpnp_pon_smpl_en_set,
+ .get = qpnp_pon_smpl_en_get,
+};
+
+module_param_cb(smpl_en, &smpl_en_ops, &smpl_en, 0644);
+
+static bool dload_on_uvlo;
+
+static int qpnp_pon_debugfs_uvlo_dload_get(char *buf,
+ const struct kernel_param *kp)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+ int rc = 0;
+ uint reg;
+
+ if (!pon)
+ return -ENODEV;
+
+ rc = regmap_read(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), ®);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read addr=%x, rc(%d)\n",
+ QPNP_PON_XVDD_RB_SPARE(pon), rc);
+ return rc;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%d",
+ !!(QPNP_PON_UVLO_DLOAD_EN & reg));
+}
+
+static int qpnp_pon_debugfs_uvlo_dload_set(const char *val,
+ const struct kernel_param *kp)
+{
+ struct qpnp_pon *pon = sys_reset_dev;
+ int rc = 0;
+ uint reg;
+
+ if (!pon)
+ return -ENODEV;
+
+ rc = param_set_bool(val, kp);
+ if (rc) {
+ pr_err("Unable to set bms_reset: %d\n", rc);
+ return rc;
+ }
+
+ rc = regmap_read(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), ®);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read addr=%x, rc(%d)\n",
+ QPNP_PON_XVDD_RB_SPARE(pon), rc);
+ return rc;
+ }
+
+ reg &= ~QPNP_PON_UVLO_DLOAD_EN;
+ if (*(bool *)kp->arg)
+ reg |= QPNP_PON_UVLO_DLOAD_EN;
+
+ rc = regmap_write(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), reg);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to write to addr=%hx, rc(%d)\n",
+ QPNP_PON_XVDD_RB_SPARE(pon), rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct kernel_param_ops dload_on_uvlo_ops = {
+ .set = qpnp_pon_debugfs_uvlo_dload_set,
+ .get = qpnp_pon_debugfs_uvlo_dload_get,
+};
+
+module_param_cb(dload_on_uvlo, &dload_on_uvlo_ops, &dload_on_uvlo, 0644);
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int qpnp_pon_debugfs_uvlo_get(void *data, u64 *val)
+{
+ struct qpnp_pon *pon = (struct qpnp_pon *) data;
+
+ *val = pon->uvlo;
+
+ return 0;
+}
+
+static int qpnp_pon_debugfs_uvlo_set(void *data, u64 val)
+{
+ struct qpnp_pon *pon = (struct qpnp_pon *) data;
+
+ if (pon->pon_trigger_reason == PON_SMPL ||
+ pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO)
+ panic("An UVLO was occurred.\n");
+ pon->uvlo = val;
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(qpnp_pon_debugfs_uvlo_fops, qpnp_pon_debugfs_uvlo_get,
+ qpnp_pon_debugfs_uvlo_set, "0x%02llx\n");
+
+static void qpnp_pon_debugfs_init(struct platform_device *pdev)
+{
+ struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev);
+ struct dentry *ent;
+
+ pon->debugfs = debugfs_create_dir(dev_name(&pdev->dev), NULL);
+ if (!pon->debugfs) {
+ dev_err(&pon->pdev->dev,
+ "Unable to create debugfs directory\n");
+ } else {
+ ent = debugfs_create_file("uvlo_panic", 0644,
+ pon->debugfs, pon, &qpnp_pon_debugfs_uvlo_fops);
+ if (!ent)
+ dev_err(&pon->pdev->dev,
+ "Unable to create uvlo_panic debugfs file.\n");
+ }
+}
+
+static void qpnp_pon_debugfs_remove(struct platform_device *pdev)
+{
+ struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev);
+
+ debugfs_remove_recursive(pon->debugfs);
+}
+
+#else
+
+static void qpnp_pon_debugfs_init(struct platform_device *pdev)
+{}
+
+static void qpnp_pon_debugfs_remove(struct platform_device *pdev)
+{}
+#endif
+
+static int read_gen2_pon_off_reason(struct qpnp_pon *pon, u16 *reason,
+ int *reason_index_offset)
+{
+ int rc;
+ int buf[2], reg;
+
+ rc = regmap_read(pon->regmap,
+ QPNP_PON_OFF_REASON(pon),
+ ®);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read PON_OFF_REASON reg rc:%d\n",
+ rc);
+ return rc;
+ }
+
+ if (reg & QPNP_GEN2_POFF_SEQ) {
+ rc = regmap_read(pon->regmap,
+ QPNP_POFF_REASON1(pon),
+ buf);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read POFF_REASON1 reg rc:%d\n",
+ rc);
+ return rc;
+ }
+ *reason = (u8)buf[0];
+ *reason_index_offset = 0;
+ } else if (reg & QPNP_GEN2_FAULT_SEQ) {
+ rc = regmap_bulk_read(pon->regmap,
+ QPNP_FAULT_REASON1(pon),
+ buf, 2);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read FAULT_REASON regs rc:%d\n",
+ rc);
+ return rc;
+ }
+ *reason = (u8)buf[0] | (u16)(buf[1] << 8);
+ *reason_index_offset = POFF_REASON_FAULT_OFFSET;
+ } else if (reg & QPNP_GEN2_S3_RESET_SEQ) {
+ rc = regmap_read(pon->regmap,
+ QPNP_S3_RESET_REASON(pon),
+ buf);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read S3_RESET_REASON reg rc:%d\n",
+ rc);
+ return rc;
+ }
+ *reason = (u8)buf[0];
+ *reason_index_offset = POFF_REASON_S3_RESET_OFFSET;
+ }
+
+ return 0;
+}
+
+static int qpnp_pon_probe(struct platform_device *pdev)
+{
+ struct qpnp_pon *pon;
+ unsigned int base;
+ struct device_node *node = NULL;
+ u32 delay = 0, s3_debounce = 0;
+ int rc, sys_reset, index;
+ int reason_index_offset = 0;
+ u8 buf[2];
+ uint pon_sts = 0;
+ u16 poff_sts = 0;
+ const char *s3_src;
+ u8 s3_src_reg;
+ unsigned long flags;
+ uint temp = 0;
+
+ pon = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_pon), GFP_KERNEL);
+ if (!pon)
+ return -ENOMEM;
+
+ pon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pon->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ sys_reset = of_property_read_bool(pdev->dev.of_node,
+ "qcom,system-reset");
+ if (sys_reset && sys_reset_dev) {
+ dev_err(&pdev->dev,
+ "qcom,system-reset property can only be specified for one device on the system\n");
+ return -EINVAL;
+ } else if (sys_reset) {
+ sys_reset_dev = pon;
+ }
+
+ pon->pdev = pdev;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+ pon->base = base;
+
+ /* get the total number of pon configurations */
+ for_each_available_child_of_node(pdev->dev.of_node, node) {
+ if (of_find_property(node, "regulator-name", NULL)) {
+ pon->num_pon_reg++;
+ } else if (of_find_property(node, "qcom,pon-type", NULL)) {
+ pon->num_pon_config++;
+ } else {
+ pr_err("Unknown sub-node\n");
+ return -EINVAL;
+ }
+ }
+
+ pr_debug("PON@SID %d: num_pon_config: %d num_pon_reg: %d\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid,
+ pon->num_pon_config, pon->num_pon_reg);
+
+ rc = pon_regulator_init(pon);
+ if (rc) {
+ dev_err(&pdev->dev, "Error in pon_regulator_init rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ if (!pon->num_pon_config)
+ /* No PON config., do not register the driver */
+ dev_info(&pdev->dev, "No PON config. specified\n");
+ else
+ pon->pon_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(struct qpnp_pon_config) *
+ pon->num_pon_config, GFP_KERNEL);
+
+ /* Read PON_PERPH_SUBTYPE register to get PON type */
+ rc = regmap_read(pon->regmap,
+ QPNP_PON_PERPH_SUBTYPE(pon),
+ &temp);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read PON_PERPH_SUBTYPE register rc: %d\n",
+ rc);
+ return rc;
+ }
+ pon->subtype = temp;
+
+ /* Check if it is rev B */
+ rc = regmap_read(pon->regmap,
+ QPNP_PON_REVISION2(pon), &temp);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read addr=%x, rc(%d)\n",
+ QPNP_PON_REVISION2(pon), rc);
+ return rc;
+ }
+
+ pon->pon_ver = temp;
+ if (is_pon_gen1(pon)) {
+ if (pon->pon_ver == 0)
+ pon->pon_ver = QPNP_PON_GEN1_V1;
+ else
+ pon->pon_ver = QPNP_PON_GEN1_V2;
+ } else if (is_pon_gen2(pon)) {
+ pon->pon_ver = QPNP_PON_GEN2;
+ } else if (pon->subtype == PON_1REG) {
+ pon->pon_ver = QPNP_PON_GEN1_V2;
+ } else {
+ dev_err(&pon->pdev->dev,
+ "Invalid PON_PERPH_SUBTYPE value %x\n",
+ pon->subtype);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: pon_subtype=%x, pon_version=%x\n", __func__,
+ pon->subtype, pon->pon_ver);
+
+ rc = qpnp_pon_store_and_clear_warm_reset(pon);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to store/clear WARM_RESET_REASONx registers rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ /* PON reason */
+ rc = regmap_read(pon->regmap, QPNP_PON_REASON1(pon), &pon_sts);
+ if (rc) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read PON_RESASON1 reg rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ index = ffs(pon_sts) - 1;
+ cold_boot = !qpnp_pon_is_warm_reset();
+ if (index >= ARRAY_SIZE(qpnp_pon_reason) || index < 0) {
+ dev_info(&pon->pdev->dev,
+ "PMIC@SID%d Power-on reason: Unknown and '%s' boot\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid,
+ cold_boot ? "cold" : "warm");
+ } else {
+ pon->pon_trigger_reason = index;
+ dev_info(&pon->pdev->dev,
+ "PMIC@SID%d Power-on reason: %s and '%s' boot\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid,
+ qpnp_pon_reason[index],
+ cold_boot ? "cold" : "warm");
+ }
+
+ /* POFF reason */
+ if (!is_pon_gen1(pon) && pon->subtype != PON_1REG) {
+ rc = read_gen2_pon_off_reason(pon, &poff_sts,
+ &reason_index_offset);
+ if (rc)
+ return rc;
+ } else {
+ rc = regmap_bulk_read(pon->regmap, QPNP_POFF_REASON1(pon),
+ buf, 2);
+ if (rc) {
+ dev_err(&pon->pdev->dev, "Unable to read POFF_REASON regs rc:%d\n",
+ rc);
+ return rc;
+ }
+ poff_sts = buf[0] | (buf[1] << 8);
+ }
+ index = ffs(poff_sts) - 1 + reason_index_offset;
+ if (index >= ARRAY_SIZE(qpnp_poff_reason) || index < 0) {
+ dev_info(&pon->pdev->dev,
+ "PMIC@SID%d: Unknown power-off reason\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid);
+ } else {
+ pon->pon_power_off_reason = index;
+ dev_info(&pon->pdev->dev,
+ "PMIC@SID%d: Power-off reason: %s\n",
+ to_spmi_device(pon->pdev->dev.parent)->usid,
+ qpnp_poff_reason[index]);
+ }
+
+ if (pon->pon_trigger_reason == PON_SMPL ||
+ pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO) {
+ if (of_property_read_bool(pdev->dev.of_node,
+ "qcom,uvlo-panic"))
+ panic("An UVLO was occurred.");
+ }
+
+ /* program s3 debounce */
+ rc = of_property_read_u32(pon->pdev->dev.of_node,
+ "qcom,s3-debounce", &s3_debounce);
+ if (rc) {
+ if (rc != -EINVAL) {
+ dev_err(&pon->pdev->dev,
+ "Unable to read s3 timer rc:%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ if (s3_debounce > QPNP_PON_S3_TIMER_SECS_MAX) {
+ dev_info(&pon->pdev->dev,
+ "Exceeded S3 max value, set it to max\n");
+ s3_debounce = QPNP_PON_S3_TIMER_SECS_MAX;
+ }
+
+ /* 0 is a special value to indicate instant s3 reset */
+ if (s3_debounce != 0)
+ s3_debounce = ilog2(s3_debounce);
+
+ /* s3 debounce is SEC_ACCESS register */
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_SEC_ACCESS(pon),
+ 0xFF, QPNP_PON_SEC_UNLOCK);
+ if (rc) {
+ dev_err(&pdev->dev, "Unable to do SEC_ACCESS rc:%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_DBC_CTL(pon),
+ QPNP_PON_S3_DBC_DELAY_MASK, s3_debounce);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to set S3 debounce rc:%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* program s3 source */
+ s3_src = "kpdpwr-and-resin";
+ rc = of_property_read_string(pon->pdev->dev.of_node,
+ "qcom,s3-src", &s3_src);
+ if (rc && rc != -EINVAL) {
+ dev_err(&pon->pdev->dev, "Unable to read s3 timer rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ if (!strcmp(s3_src, "kpdpwr"))
+ s3_src_reg = QPNP_PON_S3_SRC_KPDPWR;
+ else if (!strcmp(s3_src, "resin"))
+ s3_src_reg = QPNP_PON_S3_SRC_RESIN;
+ else if (!strcmp(s3_src, "kpdpwr-or-resin"))
+ s3_src_reg = QPNP_PON_S3_SRC_KPDPWR_OR_RESIN;
+ else /* default combination */
+ s3_src_reg = QPNP_PON_S3_SRC_KPDPWR_AND_RESIN;
+
+ /*
+ * S3 source is a write once register. If the register has
+ * been configured by bootloader then this operation will
+ * not be effective.
+ */
+ rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_SRC(pon),
+ QPNP_PON_S3_SRC_MASK, s3_src_reg);
+ if (rc) {
+ dev_err(&pdev->dev, "Unable to program s3 source rc: %d\n",
+ rc);
+ return rc;
+ }
+
+ dev_set_drvdata(&pdev->dev, pon);
+
+ INIT_DELAYED_WORK(&pon->bark_work, bark_work_func);
+
+ /* register the PON configurations */
+ rc = qpnp_pon_config_init(pon);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to initialize PON configurations rc: %d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(pon->pdev->dev.of_node,
+ "qcom,pon-dbc-delay", &delay);
+ if (rc) {
+ if (rc != -EINVAL) {
+ dev_err(&pdev->dev,
+ "Unable to read debounce delay rc: %d\n", rc);
+ return rc;
+ }
+ } else {
+ rc = qpnp_pon_set_dbc(pon, delay);
+ }
+
+ rc = of_property_read_u32(pon->pdev->dev.of_node,
+ "qcom,warm-reset-poweroff-type",
+ &pon->warm_reset_poff_type);
+ if (rc) {
+ if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read warm reset poweroff type rc: %d\n",
+ rc);
+ return rc;
+ }
+ pon->warm_reset_poff_type = -EINVAL;
+ } else if (pon->warm_reset_poff_type <= PON_POWER_OFF_RESERVED ||
+ pon->warm_reset_poff_type >= PON_POWER_OFF_MAX_TYPE) {
+ dev_err(&pdev->dev, "Invalid warm-reset-poweroff-type\n");
+ pon->warm_reset_poff_type = -EINVAL;
+ }
+
+ rc = of_property_read_u32(pon->pdev->dev.of_node,
+ "qcom,hard-reset-poweroff-type",
+ &pon->hard_reset_poff_type);
+ if (rc) {
+ if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read hard reset poweroff type rc: %d\n",
+ rc);
+ return rc;
+ }
+ pon->hard_reset_poff_type = -EINVAL;
+ } else if (pon->hard_reset_poff_type <= PON_POWER_OFF_RESERVED ||
+ pon->hard_reset_poff_type >= PON_POWER_OFF_MAX_TYPE) {
+ dev_err(&pdev->dev, "Invalid hard-reset-poweroff-type\n");
+ pon->hard_reset_poff_type = -EINVAL;
+ }
+
+ rc = of_property_read_u32(pon->pdev->dev.of_node,
+ "qcom,shutdown-poweroff-type",
+ &pon->shutdown_poff_type);
+ if (rc) {
+ if (rc != -EINVAL) {
+ dev_err(&pdev->dev, "Unable to read shutdown poweroff type rc: %d\n",
+ rc);
+ return rc;
+ }
+ pon->shutdown_poff_type = -EINVAL;
+ } else if (pon->shutdown_poff_type <= PON_POWER_OFF_RESERVED ||
+ pon->shutdown_poff_type >= PON_POWER_OFF_MAX_TYPE) {
+ dev_err(&pdev->dev, "Invalid shutdown-poweroff-type\n");
+ pon->shutdown_poff_type = -EINVAL;
+ }
+
+ rc = device_create_file(&pdev->dev, &dev_attr_debounce_us);
+ if (rc) {
+ dev_err(&pdev->dev, "sys file creation failed rc: %d\n", rc);
+ return rc;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node,
+ "qcom,secondary-pon-reset")) {
+ if (sys_reset) {
+ dev_err(&pdev->dev,
+ "qcom,system-reset property shouldn't be used along with qcom,secondary-pon-reset property\n");
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&spon_list_slock, flags);
+ list_add(&pon->list, &spon_dev_list);
+ spin_unlock_irqrestore(&spon_list_slock, flags);
+ pon->is_spon = true;
+ } else {
+ boot_reason = ffs(pon_sts);
+ }
+
+ /* config whether store the hard reset reason */
+ pon->store_hard_reset_reason = of_property_read_bool(pdev->dev.of_node,
+ "qcom,store-hard-reset-reason");
+
+ qpnp_pon_debugfs_init(pdev);
+ return 0;
+}
+
+static int qpnp_pon_remove(struct platform_device *pdev)
+{
+ struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev);
+ unsigned long flags;
+
+ device_remove_file(&pdev->dev, &dev_attr_debounce_us);
+
+ cancel_delayed_work_sync(&pon->bark_work);
+
+ if (pon->pon_input)
+ input_unregister_device(pon->pon_input);
+ qpnp_pon_debugfs_remove(pdev);
+ if (pon->is_spon) {
+ spin_lock_irqsave(&spon_list_slock, flags);
+ list_del(&pon->list);
+ spin_unlock_irqrestore(&spon_list_slock, flags);
+ }
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-power-on", },
+ {}
+};
+
+static struct platform_driver qpnp_pon_driver = {
+ .driver = {
+ .name = "qcom,qpnp-power-on",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_pon_probe,
+ .remove = qpnp_pon_remove,
+};
+
+static int __init qpnp_pon_init(void)
+{
+ return platform_driver_register(&qpnp_pon_driver);
+}
+subsys_initcall(qpnp_pon_init);
+
+static void __exit qpnp_pon_exit(void)
+{
+ return platform_driver_unregister(&qpnp_pon_driver);
+}
+module_exit(qpnp_pon_exit);
+
+MODULE_DESCRIPTION("QPNP PMIC POWER-ON driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
index 9e3c927..bae9ef2 100644
--- a/drivers/power/reset/msm-poweroff.c
+++ b/drivers/power/reset/msm-poweroff.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -23,7 +23,7 @@
#include <linux/reboot.h>
#include <linux/pm.h>
#include <linux/delay.h>
-#include <linux/qpnp/power-on.h>
+#include <linux/input/qpnp-power-on.h>
#include <linux/of_address.h>
#include <asm/cacheflush.h>
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index fdb824f..7a22d8c 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -205,6 +205,7 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(usb_hc),
POWER_SUPPLY_ATTR(usb_otg),
POWER_SUPPLY_ATTR(charge_enabled),
+ POWER_SUPPLY_ATTR(set_ship_mode),
/* Local extensions of type int64_t */
POWER_SUPPLY_ATTR(charge_counter_ext),
/* Properties of type `const char *' */
diff --git a/drivers/soc/qcom/llcc-msmskunk.c b/drivers/soc/qcom/llcc-msmskunk.c
index 65c79f7..41f55eb 100644
--- a/drivers/soc/qcom/llcc-msmskunk.c
+++ b/drivers/soc/qcom/llcc-msmskunk.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -57,24 +57,28 @@
}
static struct llcc_slice_config msmskunk_data[] = {
- SCT_ENTRY("cpuss", 1, 1, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 1),
+ SCT_ENTRY("cpuss", 1, 1, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 1),
SCT_ENTRY("vidsc0", 2, 2, 512, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
SCT_ENTRY("vidsc1", 3, 3, 512, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("rotator", 4, 4, 800, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("voice", 5, 5, 3072, 1, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("audio", 6, 6, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("modemhp", 7, 7, 1024, 2, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("modem", 8, 8, 1024, 0, 1, 0xF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("modemhw", 9, 9, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("compute", 10, 10, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("gpuhtw", 11, 11, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("gpu", 12, 12, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("mmuhwt", 13, 13, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1),
- SCT_ENTRY("sensor", 14, 14, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("compute_dma", 15, 15, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("display", 16, 16, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("videofw", 17, 17, 3072, 0, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
- SCT_ENTRY("camerafw", 18, 18, 224, 0, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("rotator", 4, 4, 563, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("voice", 5, 5, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("audio", 6, 6, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("modemhp_grow", 7, 7, 1024, 2, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("modem", 8, 8, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("modemhw", 9, 9, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("compute", 10, 10, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("gpuhtw", 11, 11, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("gpu", 12, 12, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("mmuhwt", 13, 13, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1),
+ SCT_ENTRY("sensor", 14, 14, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("compute_dma", 15, 15, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("display", 16, 16, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("videofw", 17, 17, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("camerafw", 18, 18, 256, 1, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("mss_tcm", 19, 19, 1024, 0, 0, 0x0, 0xf, 1, 0, 1, 1, 0),
+ SCT_ENTRY("modemhp_fix", 20, 20, 1024, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("modem_paging", 21, 21, 1024, 0, 1, 0xF, 0x0, 0, 0, 0, 1, 0),
+ SCT_ENTRY("audiohw", 22, 22, 3072, 1, 0, 0xFFF, 0x0, 0, 0, 0, 1, 0),
};
static int msmskunk_qcom_llcc_probe(struct platform_device *pdev)
diff --git a/drivers/soc/qcom/peripheral-loader.c b/drivers/soc/qcom/peripheral-loader.c
index 1e593e0..0dd8b26 100644
--- a/drivers/soc/qcom/peripheral-loader.c
+++ b/drivers/soc/qcom/peripheral-loader.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -627,6 +627,7 @@ static int pil_load_seg(struct pil_desc *desc, struct pil_seg *seg)
char fw_name[30];
int num = seg->num;
const struct firmware *fw = NULL;
+ void __iomem *firmware_buf;
struct pil_map_fw_info map_fw_info = {
.attrs = desc->attrs,
.region = desc->priv->region,
@@ -638,20 +639,28 @@ static int pil_load_seg(struct pil_desc *desc, struct pil_seg *seg)
if (seg->filesz) {
snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d",
desc->fw_name, num);
+ firmware_buf = desc->map_fw_mem(seg->paddr, seg->filesz,
+ map_data);
+ if (!firmware_buf) {
+ pil_err(desc, "Failed to map memory for firmware buffer\n");
+ return -ENOMEM;
+ }
+
ret = request_firmware_into_buf(&fw, fw_name, desc->dev,
- map_data, seg->filesz);
- if (ret < 0) {
+ firmware_buf, seg->filesz);
+ desc->unmap_fw_mem(firmware_buf, seg->filesz, map_data);
+
+ if (ret) {
pil_err(desc, "Failed to locate blob %s or blob is too big(rc:%d)\n",
fw_name, ret);
return ret;
}
- if (ret != seg->filesz) {
+ if (fw->size != seg->filesz) {
pil_err(desc, "Blob size %u doesn't match %lu\n",
ret, seg->filesz);
return -EPERM;
}
- ret = 0;
}
/* Zero out trailing memory */
diff --git a/drivers/soc/qcom/subsys-pil-tz.c b/drivers/soc/qcom/subsys-pil-tz.c
index a7d5d37..a46dbbd 100644
--- a/drivers/soc/qcom/subsys-pil-tz.c
+++ b/drivers/soc/qcom/subsys-pil-tz.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -1039,7 +1039,7 @@ static int pil_tz_driver_probe(struct platform_device *pdev)
if (!d->subsys_desc.no_auth) {
rc = piltz_resc_init(pdev, d);
if (rc)
- return -ENOENT;
+ return rc;
rc = of_property_read_u32(pdev->dev.of_node, "qcom,pas-id",
&d->pas_id);
diff --git a/drivers/soc/qcom/subsystem_restart.c b/drivers/soc/qcom/subsystem_restart.c
index c6e288e..cb91789 100644
--- a/drivers/soc/qcom/subsystem_restart.c
+++ b/drivers/soc/qcom/subsystem_restart.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -1697,7 +1697,6 @@ struct subsys_device *subsys_register(struct subsys_desc *desc)
if (ret) {
subsys_debugfs_remove(subsys);
put_device(&subsys->dev);
- kfree(subsys);
return ERR_PTR(ret);
}
@@ -1759,7 +1758,6 @@ struct subsys_device *subsys_register(struct subsys_desc *desc)
err_register:
subsys_debugfs_remove(subsys);
device_unregister(&subsys->dev);
- kfree(subsys);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(subsys_register);
diff --git a/include/dt-bindings/clock/qcom,cpucc-skunk.h b/include/dt-bindings/clock/qcom,cpucc-skunk.h
new file mode 100644
index 0000000..2332969
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,cpucc-skunk.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DT_BINDINGS_CLK_MSM_CPU_CC_SKUNK_H
+#define _DT_BINDINGS_CLK_MSM_CPU_CC_SKUNK_H
+
+#define L3_CLUSTER0_VOTE_CLK 0
+#define L3_CLUSTER1_VOTE_CLK 1
+#define L3_CLK 2
+#define CPU0_PWRCL_CLK 3
+#define CPU1_PWRCL_CLK 4
+#define CPU2_PWRCL_CLK 5
+#define CPU3_PWRCL_CLK 6
+#define PWRCL_CLK 7
+#define CPU4_PERFCL_CLK 8
+#define CPU5_PERFCL_CLK 9
+#define CPU6_PERFCL_CLK 10
+#define CPU7_PERFCL_CLK 11
+#define PERFCL_CLK 12
+
+#endif
diff --git a/include/linux/input/qpnp-power-on.h b/include/linux/input/qpnp-power-on.h
new file mode 100644
index 0000000..a2624ab
--- /dev/null
+++ b/include/linux/input/qpnp-power-on.h
@@ -0,0 +1,96 @@
+/* Copyright (c) 2012-2015, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef QPNP_PON_H
+#define QPNP_PON_H
+
+#include <linux/errno.h>
+
+/**
+ * enum pon_trigger_source: List of PON trigger sources
+ * %PON_SMPL: PON triggered by SMPL - Sudden Momentary Power Loss
+ * %PON_RTC: PON triggered by RTC alarm
+ * %PON_DC_CHG: PON triggered by insertion of DC charger
+ * %PON_USB_CHG: PON triggered by insertion of USB
+ * %PON_PON1: PON triggered by other PMIC (multi-PMIC option)
+ * %PON_CBLPWR_N: PON triggered by power-cable insertion
+ * %PON_KPDPWR_N: PON triggered by long press of the power-key
+ */
+enum pon_trigger_source {
+ PON_SMPL = 1,
+ PON_RTC,
+ PON_DC_CHG,
+ PON_USB_CHG,
+ PON_PON1,
+ PON_CBLPWR_N,
+ PON_KPDPWR_N,
+};
+
+/**
+ * enum pon_power_off_type: Possible power off actions to perform
+ * %PON_POWER_OFF_RESERVED: Reserved, not used
+ * %PON_POWER_OFF_WARM_RESET: Reset the MSM but not all PMIC peripherals
+ * %PON_POWER_OFF_SHUTDOWN: Shutdown the MSM and PMIC completely
+ * %PON_POWER_OFF_HARD_RESET: Reset the MSM and all PMIC peripherals
+ */
+enum pon_power_off_type {
+ PON_POWER_OFF_RESERVED = 0x00,
+ PON_POWER_OFF_WARM_RESET = 0x01,
+ PON_POWER_OFF_SHUTDOWN = 0x04,
+ PON_POWER_OFF_HARD_RESET = 0x07,
+ PON_POWER_OFF_MAX_TYPE = 0x10,
+};
+
+enum pon_restart_reason {
+ PON_RESTART_REASON_UNKNOWN = 0x00,
+ PON_RESTART_REASON_RECOVERY = 0x01,
+ PON_RESTART_REASON_BOOTLOADER = 0x02,
+ PON_RESTART_REASON_RTC = 0x03,
+ PON_RESTART_REASON_DMVERITY_CORRUPTED = 0x04,
+ PON_RESTART_REASON_DMVERITY_ENFORCE = 0x05,
+ PON_RESTART_REASON_KEYS_CLEAR = 0x06,
+};
+
+#ifdef CONFIG_INPUT_QPNP_POWER_ON
+int qpnp_pon_system_pwr_off(enum pon_power_off_type type);
+int qpnp_pon_is_warm_reset(void);
+int qpnp_pon_trigger_config(enum pon_trigger_source pon_src, bool enable);
+int qpnp_pon_wd_config(bool enable);
+int qpnp_pon_set_restart_reason(enum pon_restart_reason reason);
+bool qpnp_pon_check_hard_reset_stored(void);
+
+#else
+static int qpnp_pon_system_pwr_off(enum pon_power_off_type type)
+{
+ return -ENODEV;
+}
+static inline int qpnp_pon_is_warm_reset(void) { return -ENODEV; }
+static inline int qpnp_pon_trigger_config(enum pon_trigger_source pon_src,
+ bool enable)
+{
+ return -ENODEV;
+}
+int qpnp_pon_wd_config(bool enable)
+{
+ return -ENODEV;
+}
+static inline int qpnp_pon_set_restart_reason(enum pon_restart_reason reason)
+{
+ return -ENODEV;
+}
+static inline bool qpnp_pon_check_hard_reset_stored(void)
+{
+ return false;
+}
+#endif
+
+#endif
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index ecfb4cac..c30fd4b 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -153,6 +153,7 @@ enum power_supply_property {
POWER_SUPPLY_PROP_USB_HC,
POWER_SUPPLY_PROP_USB_OTG,
POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_SET_SHIP_MODE,
/* Local extensions of type int64_t */
POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT,
/* Properties of type `const char *' */
diff --git a/include/uapi/linux/sysctl.h b/include/uapi/linux/sysctl.h
index d2b1215..e854785 100644
--- a/include/uapi/linux/sysctl.h
+++ b/include/uapi/linux/sysctl.h
@@ -152,6 +152,8 @@ enum
KERN_NMI_WATCHDOG=75, /* int: enable/disable nmi watchdog */
KERN_PANIC_ON_NMI=76, /* int: whether we will panic on an unrecovered */
KERN_PANIC_ON_WARN=77, /* int: call panic() in WARN() functions */
+ KERN_BOOT_REASON=78, /* int: identify reason system was booted */
+ KERN_COLD_BOOT=79, /* int: identify if system cold booted */
};
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index fe65371..337cb1e 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1398,6 +1398,23 @@ static struct ctl_table kern_table[] = {
.extra2 = &one,
},
#endif
+#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
+ {
+ .procname = "boot_reason",
+ .data = &boot_reason,
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = proc_dointvec,
+ },
+
+ {
+ .procname = "cold_boot",
+ .data = &cold_boot,
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = proc_dointvec,
+ },
+#endif
{ }
};
diff --git a/kernel/sysctl_binary.c b/kernel/sysctl_binary.c
index 6eb99c1..33fba7f 100644
--- a/kernel/sysctl_binary.c
+++ b/kernel/sysctl_binary.c
@@ -139,6 +139,8 @@ static const struct bin_table bin_kern_table[] = {
{ CTL_INT, KERN_MAX_LOCK_DEPTH, "max_lock_depth" },
{ CTL_INT, KERN_PANIC_ON_NMI, "panic_on_unrecovered_nmi" },
{ CTL_INT, KERN_PANIC_ON_WARN, "panic_on_warn" },
+ { CTL_INT, KERN_BOOT_REASON, "boot_reason" },
+ { CTL_INT, KERN_COLD_BOOT, "cold_boot" },
{}
};
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index 21c3ef0..ab07789 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -180,7 +180,11 @@ int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir,
dev_dbg(be->dev, "ASoC: BE %s event %d dir %d\n",
be->dai_link->name, event, dir);
-
+ if ((event == SND_SOC_DAPM_STREAM_STOP) &&
+ (be->dpcm[dir].users >= 1)) {
+ pr_debug("%s Don't close BE\n", __func__);
+ continue;
+ }
snd_soc_dapm_stream_event(be, dir, event);
}