summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:05:21 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:05:21 +0800
commit08dcfadc9a22bdb8cd37bc1ce38bc98d202cd857 (patch)
tree16d9a0e21c69f77d63a53dda38b194ffc03c7743
parent433035eb79767026560cec744907792f1e3ea828 (diff)
parente09f1585c55551d496f1e0920342491dd93e8442 (diff)
Merge branch 'pm/next' into next
* pm/next: (54 commits) drivers/soc/fsl: add EPU FSM configuration for deep sleep fsl_pmc: update device bindings soc: fsl: add RCPM driver Documentation: dt: binding: fsl: Add 'little-endian' and update Chassis define MLK-22992 firmware: imx: scu-pd: fix wu_num ...
-rw-r--r--Documentation/devicetree/bindings/power/fsl,imx8m-genpd.txt46
-rw-r--r--Documentation/devicetree/bindings/powerpc/fsl/pmc.txt59
-rw-r--r--Documentation/devicetree/bindings/soc/fsl/rcpm.txt14
-rw-r--r--drivers/base/dd.c16
-rw-r--r--drivers/base/power/domain.c58
-rw-r--r--drivers/base/power/wakeup.c54
-rw-r--r--drivers/char/Kconfig1
-rw-r--r--drivers/char/Makefile1
-rw-r--r--drivers/char/imx_amp/Kconfig9
-rw-r--r--drivers/char/imx_amp/Makefile5
-rw-r--r--drivers/char/imx_amp/imx_sema4.c413
-rw-r--r--drivers/cpufreq/Kconfig.arm8
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/imx-cpufreq-dt.c20
-rw-r--r--drivers/cpufreq/imx6q-cpufreq.c143
-rw-r--r--drivers/cpufreq/imx7ulp-cpufreq.c260
-rwxr-xr-x[-rw-r--r--]drivers/firmware/imx/scu-pd.c203
-rw-r--r--drivers/power/supply/Kconfig8
-rw-r--r--drivers/power/supply/Makefile1
-rw-r--r--drivers/power/supply/sabresd_battery.c1014
-rw-r--r--drivers/soc/fsl/Kconfig16
-rw-r--r--drivers/soc/fsl/Makefile2
-rw-r--r--drivers/soc/fsl/rcpm.c151
-rw-r--r--drivers/soc/fsl/sleep_fsm.c279
-rw-r--r--drivers/soc/fsl/sleep_fsm.h130
-rw-r--r--drivers/soc/imx/Kconfig6
-rw-r--r--drivers/soc/imx/Makefile1
-rw-r--r--drivers/soc/imx/gpc.c34
-rw-r--r--drivers/soc/imx/imx8m_pm_domains.c224
-rw-r--r--include/linux/imx_sema4.h83
-rw-r--r--include/linux/pm_domain.h1
-rw-r--r--include/linux/pm_wakeup.h9
-rw-r--r--include/linux/power/sabresd_battery.h65
-rw-r--r--include/soc/imx/imx_sip.h12
34 files changed, 3260 insertions, 87 deletions
diff --git a/Documentation/devicetree/bindings/power/fsl,imx8m-genpd.txt b/Documentation/devicetree/bindings/power/fsl,imx8m-genpd.txt
new file mode 100644
index 000000000000..a92a7103de38
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/fsl,imx8m-genpd.txt
@@ -0,0 +1,46 @@
+Device Tree Bindings for Freescale i.MX8M Generic Power Domain
+==============================================================
+The binding for the i.MX8M Generic power Domain[1].
+
+[1] Documentation/devicetree/bindings/power/power_domain.txt
+
+Required properties:
+
+ - compatible: should be of:
+ - "fsl,imx8m-power-domain"
+ - #power-domain-cells: Number of cells in a PM domain Specifier, must be 0
+ - domain-index: should be the domain index number need to pass to TF-A
+ - domain-name: the name of this pm domain
+
+Optional properties:
+ - clocks: a number of phandles to clocks that need to be enabled during
+ domain power-up sequence to ensure reset propagation into devices
+ located inside this power domain
+ - power-supply: Power supply used to power the domain
+ - parent-domains: the phandle to the parent power domain
+
+example:
+ vpu_g1_pd: vpug1-pd {
+ compatible = "fsl,imx8mm-pm-domain";
+ #power-domain-cells = <0>;
+ domain-index = <6>;
+ domain-name = "vpu_g1";
+ parent-domains = <&vpumix_pd>;
+ clocks = <&clk IMX8MM_CLK_VPU_G1_ROOT>;
+ };
+
+
+Specifying Power domain for IP modules
+======================================
+
+IP cores belonging to a power domain should contain a 'power-domains'
+property that is a phandle for PGC node representing the domain.
+
+Example of a device that is part of the vpu_g1 power domain:
+ vpu_g1: vpu_g1@38300000 {
+ /* ... */
+ interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "irq_hantro";
+ /* ... */
+ power-domains = <&vpu_g1_pd>;
+ };
diff --git a/Documentation/devicetree/bindings/powerpc/fsl/pmc.txt b/Documentation/devicetree/bindings/powerpc/fsl/pmc.txt
index 07256b7ffcaa..f1f749fc253e 100644
--- a/Documentation/devicetree/bindings/powerpc/fsl/pmc.txt
+++ b/Documentation/devicetree/bindings/powerpc/fsl/pmc.txt
@@ -9,15 +9,20 @@ Properties:
"fsl,mpc8548-pmc" should be listed for any chip whose PMC is
compatible. "fsl,mpc8536-pmc" should also be listed for any chip
- whose PMC is compatible, and implies deep-sleep capability.
+ whose PMC is compatible, and implies deep-sleep capability and
+ wake on user defined packet(wakeup on ARP).
+
+ "fsl,p1022-pmc" should be listed for any chip whose PMC is
+ compatible, and implies lossless Ethernet capability during sleep.
"fsl,mpc8641d-pmc" should be listed for any chip whose PMC is
compatible; all statements below that apply to "fsl,mpc8548-pmc" also
apply to "fsl,mpc8641d-pmc".
Compatibility does not include bit assignments in SCCR/PMCDR/DEVDISR; these
- bit assignments are indicated via the sleep specifier in each device's
- sleep property.
+ bit assignments are indicated via the clock nodes. Device which has a
+ controllable clock source should have a "fsl,pmc-handle" property pointing
+ to the clock node.
- reg: For devices compatible with "fsl,mpc8349-pmc", the first resource
is the PMC block, and the second resource is the Clock Configuration
@@ -33,31 +38,35 @@ Properties:
this is a phandle to an "fsl,gtm" node on which timer 4 can be used as
a wakeup source from deep sleep.
-Sleep specifiers:
+Clock nodes:
+The clock nodes are to describe the masks in PM controller registers for each
+soc clock.
+- fsl,pmcdr-mask: For "fsl,mpc8548-pmc"-compatible devices, the mask will be
+ ORed into PMCDR before suspend if the device using this clock is the wake-up
+ source and need to be running during low power mode; clear the mask if
+ otherwise.
- fsl,mpc8349-pmc: Sleep specifiers consist of one cell. For each bit
- that is set in the cell, the corresponding bit in SCCR will be saved
- and cleared on suspend, and restored on resume. This sleep controller
- supports disabling and resuming devices at any time.
+- fsl,sccr-mask: For "fsl,mpc8349-pmc"-compatible devices, the corresponding
+ bit specified by the mask in SCCR will be saved and cleared on suspend, and
+ restored on resume.
- fsl,mpc8536-pmc: Sleep specifiers consist of three cells, the third of
- which will be ORed into PMCDR upon suspend, and cleared from PMCDR
- upon resume. The first two cells are as described for fsl,mpc8578-pmc.
- This sleep controller only supports disabling devices during system
- sleep, or permanently.
-
- fsl,mpc8548-pmc: Sleep specifiers consist of one or two cells, the
- first of which will be ORed into DEVDISR (and the second into
- DEVDISR2, if present -- this cell should be zero or absent if the
- hardware does not have DEVDISR2) upon a request for permanent device
- disabling. This sleep controller does not support configuring devices
- to disable during system sleep (unless supported by another compatible
- match), or dynamically.
+- fsl,devdisr-mask: Contain one or two cells, depending on the availability of
+ DEVDISR2 register. For compatible devices, the mask will be ORed into DEVDISR
+ or DEVDISR2 when the clock should be permenently disabled.
Example:
- power@b00 {
- compatible = "fsl,mpc8313-pmc", "fsl,mpc8349-pmc";
- reg = <0xb00 0x100 0xa00 0x100>;
- interrupts = <80 8>;
+ power@e0070 {
+ compatible = "fsl,mpc8536-pmc", "fsl,mpc8548-pmc";
+ reg = <0xe0070 0x20>;
+
+ etsec1_clk: soc-clk@24 {
+ fsl,pmcdr-mask = <0x00000080>;
+ };
+ etsec2_clk: soc-clk@25 {
+ fsl,pmcdr-mask = <0x00000040>;
+ };
+ etsec3_clk: soc-clk@26 {
+ fsl,pmcdr-mask = <0x00000020>;
+ };
};
diff --git a/Documentation/devicetree/bindings/soc/fsl/rcpm.txt b/Documentation/devicetree/bindings/soc/fsl/rcpm.txt
index e284e4e1ccd5..5a33619d881d 100644
--- a/Documentation/devicetree/bindings/soc/fsl/rcpm.txt
+++ b/Documentation/devicetree/bindings/soc/fsl/rcpm.txt
@@ -5,7 +5,7 @@ and power management.
Required properites:
- reg : Offset and length of the register set of the RCPM block.
- - fsl,#rcpm-wakeup-cells : The number of IPPDEXPCR register cells in the
+ - #fsl,rcpm-wakeup-cells : The number of IPPDEXPCR register cells in the
fsl,rcpm-wakeup property.
- compatible : Must contain a chip-specific RCPM block compatible string
and (if applicable) may contain a chassis-version RCPM compatible
@@ -20,6 +20,7 @@ Required properites:
* "fsl,qoriq-rcpm-1.0": for chassis 1.0 rcpm
* "fsl,qoriq-rcpm-2.0": for chassis 2.0 rcpm
* "fsl,qoriq-rcpm-2.1": for chassis 2.1 rcpm
+ * "fsl,qoriq-rcpm-2.1+": for chassis 2.1+ rcpm
All references to "1.0" and "2.0" refer to the QorIQ chassis version to
which the chip complies.
@@ -27,14 +28,19 @@ Chassis Version Example Chips
--------------- -------------------------------
1.0 p4080, p5020, p5040, p2041, p3041
2.0 t4240, b4860, b4420
-2.1 t1040, ls1021
+2.1 t1040,
+2.1+ ls1021a, ls1012a, ls1043a, ls1046a
+
+Optional properties:
+ - little-endian : RCPM register block is Little Endian. Without it RCPM
+ will be Big Endian (default case).
Example:
The RCPM node for T4240:
rcpm: global-utilities@e2000 {
compatible = "fsl,t4240-rcpm", "fsl,qoriq-rcpm-2.0";
reg = <0xe2000 0x1000>;
- fsl,#rcpm-wakeup-cells = <2>;
+ #fsl,rcpm-wakeup-cells = <2>;
};
* Freescale RCPM Wakeup Source Device Tree Bindings
@@ -44,7 +50,7 @@ can be used as a wakeup source.
- fsl,rcpm-wakeup: Consists of a phandle to the rcpm node and the IPPDEXPCR
register cells. The number of IPPDEXPCR register cells is defined in
- "fsl,#rcpm-wakeup-cells" in the rcpm node. The first register cell is
+ "#fsl,rcpm-wakeup-cells" in the rcpm node. The first register cell is
the bit mask that should be set in IPPDEXPCR0, and the second register
cell is for IPPDEXPCR1, and so on.
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index d811e60610d3..876366916d1b 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -102,14 +102,6 @@ static void deferred_probe_work_func(struct work_struct *work)
*/
mutex_unlock(&deferred_probe_mutex);
- /*
- * Force the device to the end of the dpm_list since
- * the PM code assumes that the order we add things to
- * the list is a good order for suspend but deferred
- * probe makes that very unsafe.
- */
- device_pm_move_to_tail(dev);
-
dev_dbg(dev, "Retrying from deferred list\n");
bus_probe_device(dev);
mutex_lock(&deferred_probe_mutex);
@@ -381,6 +373,14 @@ static void driver_bound(struct device *dev)
device_pm_check_callbacks(dev);
/*
+ * Force the device to the end of the dpm_list since
+ * the PM code assumes that the order we add things to
+ * the list is a good order for suspend but deferred
+ * probe makes that very unsafe.
+ */
+ device_pm_move_to_tail(dev);
+
+ /*
* Make sure the device is no longer in one of the deferred lists and
* kick off retrying all pending devices
*/
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index cc85e87eaf05..2422fa500caa 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -448,6 +448,9 @@ static int _genpd_power_off(struct generic_pm_domain *genpd, bool timed)
if (!genpd->power_off)
return 0;
+ if (atomic_read(&genpd->sd_count) > 0)
+ return -EBUSY;
+
if (!timed)
return genpd->power_off(genpd);
@@ -497,6 +500,7 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
struct pm_domain_data *pdd;
struct gpd_link *link;
unsigned int not_suspended = 0;
+ int ret;
/*
* Do not try to power off the domain in the following situations:
@@ -544,24 +548,21 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
if (!genpd->gov)
genpd->state_idx = 0;
- if (genpd->power_off) {
- int ret;
+ /* Choose the deepest state if no devices using this domain */
+ if (!genpd->device_count)
+ genpd->state_idx = genpd->state_count - 1;
- if (atomic_read(&genpd->sd_count) > 0)
- return -EBUSY;
-
- /*
- * If sd_count > 0 at this point, one of the subdomains hasn't
- * managed to call genpd_power_on() for the master yet after
- * incrementing it. In that case genpd_power_on() will wait
- * for us to drop the lock, so we can call .power_off() and let
- * the genpd_power_on() restore power for us (this shouldn't
- * happen very often).
- */
- ret = _genpd_power_off(genpd, true);
- if (ret)
- return ret;
- }
+ /*
+ * If sd_count > 0 at this point, one of the subdomains hasn't
+ * managed to call genpd_power_on() for the master yet after
+ * incrementing it. In that case genpd_power_on() will wait
+ * for us to drop the lock, so we can call .power_off() and let
+ * the genpd_power_on() restore power for us (this shouldn't
+ * happen very often).
+ */
+ ret = _genpd_power_off(genpd, true);
+ if (ret)
+ return ret;
genpd->status = GPD_STATE_POWER_OFF;
genpd_update_accounting(genpd);
@@ -960,11 +961,20 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
{
struct gpd_link *link;
- if (!genpd_status_on(genpd) || genpd_is_always_on(genpd))
+ /*
+ * Give the power domain a chance to switch to the deepest state in
+ * case it's already off but in an intermediate low power state.
+ */
+ genpd->state_idx_saved = genpd->state_idx;
+
+ if (genpd_is_always_on(genpd))
return;
- if (genpd->suspended_count != genpd->device_count
- || atomic_read(&genpd->sd_count) > 0)
+ if (!genpd_status_on(genpd) &&
+ genpd->state_idx == (genpd->state_count - 1))
+ return;
+
+ if (genpd->suspended_count != genpd->device_count)
return;
/* Choose the deepest state when suspending */
@@ -972,6 +982,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
if (_genpd_power_off(genpd, false))
return;
+ if (genpd->status == GPD_STATE_POWER_OFF)
+ return;
+
genpd->status = GPD_STATE_POWER_OFF;
list_for_each_entry(link, &genpd->slave_links, slave_node) {
@@ -1019,6 +1032,9 @@ static void genpd_sync_power_on(struct generic_pm_domain *genpd, bool use_lock,
_genpd_power_on(genpd, false);
+ /* restore save power domain state after resume */
+ genpd->state_idx = genpd->state_idx_saved;
+
genpd->status = GPD_STATE_ACTIVE;
}
@@ -1829,7 +1845,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
return ret;
}
} else if (!gov && genpd->state_count > 1) {
- pr_warn("%s: no governor for states\n", genpd->name);
+ pr_debug("%s: no governor for states\n", genpd->name);
}
device_initialize(&genpd->dev);
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 5817b51d2b15..70a9edb5f525 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -248,6 +248,60 @@ void wakeup_source_unregister(struct wakeup_source *ws)
EXPORT_SYMBOL_GPL(wakeup_source_unregister);
/**
+ * wakeup_sources_read_lock - Lock wakeup source list for read.
+ *
+ * Returns an index of srcu lock for struct wakeup_srcu.
+ * This index must be passed to the matching wakeup_sources_read_unlock().
+ */
+int wakeup_sources_read_lock(void)
+{
+ return srcu_read_lock(&wakeup_srcu);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_read_lock);
+
+/**
+ * wakeup_sources_read_unlock - Unlock wakeup source list.
+ * @idx: return value from corresponding wakeup_sources_read_lock()
+ */
+void wakeup_sources_read_unlock(int idx)
+{
+ srcu_read_unlock(&wakeup_srcu, idx);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock);
+
+/**
+ * wakeup_sources_walk_start - Begin a walk on wakeup source list
+ *
+ * Returns first object of the list of wakeup sources.
+ *
+ * Note that to be safe, wakeup sources list needs to be locked by calling
+ * wakeup_source_read_lock() for this.
+ */
+struct wakeup_source *wakeup_sources_walk_start(void)
+{
+ struct list_head *ws_head = &wakeup_sources;
+
+ return list_entry_rcu(ws_head->next, struct wakeup_source, entry);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_walk_start);
+
+/**
+ * wakeup_sources_walk_next - Get next wakeup source from the list
+ * @ws: Previous wakeup source object
+ *
+ * Note that to be safe, wakeup sources list needs to be locked by calling
+ * wakeup_source_read_lock() for this.
+ */
+struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws)
+{
+ struct list_head *ws_head = &wakeup_sources;
+
+ return list_next_or_null_rcu(ws_head, &ws->entry,
+ struct wakeup_source, entry);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_walk_next);
+
+/**
* device_wakeup_attach - Attach a wakeup source object to a device object.
* @dev: Device to handle.
* @ws: Wakeup source object to attach to @dev.
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index df0fc997dc3e..b2b84096f2a7 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -535,6 +535,7 @@ config ADI
and SSM (Silicon Secured Memory). Intended consumers of this
driver include crash and makedumpfile.
+source "drivers/char/imx_amp/Kconfig"
endmenu
config RANDOM_TRUST_CPU
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 7c5ea6f9df14..acbe489829c9 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -52,3 +52,4 @@ js-rtc-y = rtc.o
obj-$(CONFIG_XILLYBUS) += xillybus/
obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o
obj-$(CONFIG_ADI) += adi.o
+obj-$(CONFIG_HAVE_IMX_AMP) += imx_amp/
diff --git a/drivers/char/imx_amp/Kconfig b/drivers/char/imx_amp/Kconfig
new file mode 100644
index 000000000000..1f892f8daccb
--- /dev/null
+++ b/drivers/char/imx_amp/Kconfig
@@ -0,0 +1,9 @@
+#
+# imx mcc
+#
+
+config IMX_SEMA4
+ bool "IMX SEMA4 driver"
+ depends on SOC_IMX6SX
+ help
+ Support for IMX SEMA4 driver, most people should say N here.
diff --git a/drivers/char/imx_amp/Makefile b/drivers/char/imx_amp/Makefile
new file mode 100644
index 000000000000..4e7a91691b39
--- /dev/null
+++ b/drivers/char/imx_amp/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for imx mcc
+#
+#
+obj-$(CONFIG_IMX_SEMA4) += imx_sema4.o
diff --git a/drivers/char/imx_amp/imx_sema4.c b/drivers/char/imx_amp/imx_sema4.c
new file mode 100644
index 000000000000..412202f11cbb
--- /dev/null
+++ b/drivers/char/imx_amp/imx_sema4.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/imx_sema4.h>
+
+static struct imx_sema4_mutex_device *imx6_sema4;
+
+/*!
+ * \brief mutex create function.
+ *
+ * This function allocates imx_sema4_mutex structure and returns a handle
+ * to it. The mutex to be created is identified by SEMA4 device number and mutex
+ * (gate) number. The handle is used to reference the created mutex in calls to
+ * other imx_sema4_mutex API functions. This function is to be called only
+ * once for each mutex.
+ *
+ * \param[in] dev_num SEMA4 device (module) number.
+ * \param[in] mutex_num Mutex (gate) number.
+ *
+ * \return NULL (Failure.)
+ * \return imx_sema4_mutex (Success.)
+ */
+struct imx_sema4_mutex *
+imx_sema4_mutex_create(u32 dev_num, u32 mutex_num)
+{
+ struct imx_sema4_mutex *mutex_ptr = NULL;
+
+ if (mutex_num >= SEMA4_NUM_GATES || dev_num >= SEMA4_NUM_DEVICES)
+ goto out;
+
+ if (imx6_sema4->cpine_val & (1 < mutex_num)) {
+ pr_err("Error: requiring a allocated sema4.\n");
+ pr_err("mutex_num %d cpine_val 0x%08x.\n",
+ mutex_num, imx6_sema4->cpine_val);
+ }
+ mutex_ptr = kzalloc(sizeof(*mutex_ptr), GFP_KERNEL);
+ if (!mutex_ptr)
+ goto out;
+ imx6_sema4->mutex_ptr[mutex_num] = mutex_ptr;
+ imx6_sema4->alloced |= 1 < mutex_num;
+ imx6_sema4->cpine_val |= idx_sema4[mutex_num];
+ writew(imx6_sema4->cpine_val, imx6_sema4->ioaddr + SEMA4_CP0INE);
+
+ mutex_ptr->valid = CORE_MUTEX_VALID;
+ mutex_ptr->gate_num = mutex_num;
+ init_waitqueue_head(&mutex_ptr->wait_q);
+
+out:
+ return mutex_ptr;
+}
+EXPORT_SYMBOL(imx_sema4_mutex_create);
+
+/*!
+ * \brief mutex destroy function.
+ *
+ * This function destroys a mutex.
+ *
+ * \param[in] mutex_ptr Pointer to mutex structure.
+ *
+ * \return MQX_COMPONENT_DOES_NOT_EXIST (mutex component not installed.)
+ * \return MQX_INVALID_PARAMETER (Wrong input parameter.)
+ * \return COREMUTEX_OK (Success.)
+ *
+ */
+int imx_sema4_mutex_destroy(struct imx_sema4_mutex *mutex_ptr)
+{
+ u32 mutex_num;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ mutex_num = mutex_ptr->gate_num;
+ if ((imx6_sema4->cpine_val & idx_sema4[mutex_num]) == 0) {
+ pr_err("Error: trying to destroy a un-allocated sema4.\n");
+ pr_err("mutex_num %d cpine_val 0x%08x.\n",
+ mutex_num, imx6_sema4->cpine_val);
+ }
+ imx6_sema4->mutex_ptr[mutex_num] = NULL;
+ imx6_sema4->alloced &= ~(1 << mutex_num);
+ imx6_sema4->cpine_val &= ~(idx_sema4[mutex_num]);
+ writew(imx6_sema4->cpine_val, imx6_sema4->ioaddr + SEMA4_CP0INE);
+
+ kfree(mutex_ptr);
+
+ return 0;
+}
+EXPORT_SYMBOL(imx_sema4_mutex_destroy);
+
+/*!
+ * \brief Lock the mutex, shouldn't be interruted by INT.
+ *
+ * This function attempts to lock a mutex. If the mutex is already locked
+ * by another task the function return -EBUSY, and tell invoker wait until
+ * it is possible to lock the mutex.
+ *
+ * \param[in] mutex_ptr Pointer to mutex structure.
+ *
+ * \return MQX_INVALID_POINTER (Wrong pointer to the mutex structure provided.)
+ * \return COREMUTEX_OK (mutex successfully locked.)
+ *
+ * \see imx_sema4_mutex_unlock
+ */
+int _imx_sema4_mutex_lock(struct imx_sema4_mutex *mutex_ptr)
+{
+ int ret = 0, i = 0;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ i = mutex_ptr->gate_num;
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* Check to see if this core already own it */
+ if (mutex_ptr->gate_val == SEMA4_A9_LOCK) {
+ /* return -EBUSY, invoker should be in sleep, and re-lock ag */
+ pr_err("%s -> %s %d already locked, wait! num %d val %d.\n",
+ __FILE__, __func__, __LINE__,
+ i, mutex_ptr->gate_val);
+ ret = -EBUSY;
+ goto out;
+ } else {
+ /* try to lock the mutex */
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_A9_LOCK;
+ writeb(mutex_ptr->gate_val, imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* double check the mutex is locked, otherwise, return -EBUSY */
+ if (mutex_ptr->gate_val != SEMA4_A9_LOCK) {
+ pr_debug("wait-locked num %d val %d.\n",
+ i, mutex_ptr->gate_val);
+ ret = -EBUSY;
+ }
+ }
+out:
+ return ret;
+}
+
+/* !
+ * \brief Try to lock the core mutex.
+ *
+ * This function attempts to lock a mutex. If the mutex is successfully locked
+ * for the calling task, SEMA4_A9_LOCK is returned. If the mutex is already
+ * locked by another task, the function does not block but rather returns
+ * negative immediately.
+ *
+ * \param[in] mutex_ptr Pointer to core_mutex structure.
+ *
+ * \return SEMA4_A9_LOCK (mutex successfully locked.)
+ * \return negative (mutex not locked.)
+ *
+ */
+int imx_sema4_mutex_trylock(struct imx_sema4_mutex *mutex_ptr)
+{
+ int ret = 0;
+
+ ret = _imx_sema4_mutex_lock(mutex_ptr);
+ if (ret == 0)
+ return SEMA4_A9_LOCK;
+ else
+ return ret;
+}
+EXPORT_SYMBOL(imx_sema4_mutex_trylock);
+
+/*!
+ * \brief Invoke _imx_sema4_mutex_lock to lock the mutex.
+ *
+ * This function attempts to lock a mutex. If the mutex is already locked
+ * by another task the function, sleep itself and schedule out.
+ * Wait until it is possible to lock the mutex.
+ *
+ * Invoker should add its own wait queue into the wait queue header of the
+ * required semaphore, set TASK_INTERRUPTIBLE and sleep on itself by
+ * schedule() when the lock is failed. Re-try to lock the semaphore when
+ * it is woke up by the sema4 isr.
+ *
+ * \param[in] mutex_ptr Pointer to mutex structure.
+ *
+ * \return SEMA4_A9_LOCK (mutex successfully locked.)
+ *
+ * \see imx_sema4_mutex_unlock
+ */
+int imx_sema4_mutex_lock(struct imx_sema4_mutex *mutex_ptr)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&imx6_sema4->lock, flags);
+ ret = _imx_sema4_mutex_lock(mutex_ptr);
+ spin_unlock_irqrestore(&imx6_sema4->lock, flags);
+ while (-EBUSY == ret) {
+ spin_lock_irqsave(&imx6_sema4->lock, flags);
+ ret = _imx_sema4_mutex_lock(mutex_ptr);
+ spin_unlock_irqrestore(&imx6_sema4->lock, flags);
+ if (ret == 0)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(imx_sema4_mutex_lock);
+
+/*!
+ * \brief Unlock the mutex.
+ *
+ * This function unlocks the specified mutex.
+ *
+ * \param[in] mutex_ptr Pointer to mutex structure.
+ *
+ * \return -EINVAL (Wrong pointer to the mutex structure provided.)
+ * \return -EINVAL (This mutex has not been locked by this core.)
+ * \return 0 (mutex successfully unlocked.)
+ *
+ * \see imx_sema4_mutex_lock
+ */
+int imx_sema4_mutex_unlock(struct imx_sema4_mutex *mutex_ptr)
+{
+ int ret = 0, i = 0;
+
+ if ((mutex_ptr == NULL) || (mutex_ptr->valid != CORE_MUTEX_VALID))
+ return -EINVAL;
+
+ i = mutex_ptr->gate_num;
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* make sure it is locked by this core */
+ if (mutex_ptr->gate_val != SEMA4_A9_LOCK) {
+ pr_err("%d Trying to unlock an unlock mutex.\n", __LINE__);
+ ret = -EINVAL;
+ goto out;
+ }
+ /* unlock it */
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ writeb(mutex_ptr->gate_val | SEMA4_UNLOCK, imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ /* make sure it is locked by this core */
+ if (mutex_ptr->gate_val == SEMA4_A9_LOCK)
+ pr_err("%d ERROR, failed to unlock the mutex.\n", __LINE__);
+
+out:
+ return ret;
+}
+EXPORT_SYMBOL(imx_sema4_mutex_unlock);
+
+/*
+ * isr used by SEMA4, wake up the sleep tasks if there are the tasks waiting
+ * for locking semaphore.
+ * FIXME the bits order of the gatn, cpnie, cpnntf are not exact identified yet!
+ */
+static irqreturn_t imx_sema4_isr(int irq, void *dev_id)
+{
+ int i;
+ struct imx_sema4_mutex *mutex_ptr;
+ unsigned int mask;
+ struct imx_sema4_mutex_device *imx6_sema4 = dev_id;
+
+ imx6_sema4->cpntf_val = readw(imx6_sema4->ioaddr + SEMA4_CP0NTF);
+ for (i = 0; i < SEMA4_NUM_GATES; i++) {
+ mask = idx_sema4[i];
+ if ((imx6_sema4->cpntf_val) & mask) {
+ mutex_ptr = imx6_sema4->mutex_ptr[i];
+ /*
+ * An interrupt is pending on this mutex, the only way
+ * to clear it is to lock it (either by this core or
+ * another).
+ */
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_A9_LOCK;
+ writeb(mutex_ptr->gate_val, imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val = readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= SEMA4_GATE_MASK;
+ if (mutex_ptr->gate_val == SEMA4_A9_LOCK) {
+ /*
+ * wake up the wait queue, whatever there
+ * are wait task or not.
+ * NOTE: check gate is locted or not in
+ * sema4_lock func by wait task.
+ */
+ mutex_ptr->gate_val =
+ readb(imx6_sema4->ioaddr + i);
+ mutex_ptr->gate_val &= (~SEMA4_GATE_MASK);
+ mutex_ptr->gate_val |= SEMA4_UNLOCK;
+
+ writeb(mutex_ptr->gate_val,
+ imx6_sema4->ioaddr + i);
+ wake_up(&mutex_ptr->wait_q);
+ } else {
+ pr_debug("can't lock gate%d %s retry!\n", i,
+ mutex_ptr->gate_val ?
+ "locked by m4" : "");
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const struct of_device_id imx_sema4_dt_ids[] = {
+ { .compatible = "fsl,imx6sx-sema4", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sema4_dt_ids);
+
+static int imx_sema4_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ imx6_sema4 = devm_kzalloc(&pdev->dev, sizeof(*imx6_sema4), GFP_KERNEL);
+ if (!imx6_sema4)
+ return -ENOMEM;
+
+ imx6_sema4->dev = &pdev->dev;
+ imx6_sema4->cpine_val = 0;
+ spin_lock_init(&imx6_sema4->lock);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR(res)) {
+ dev_err(&pdev->dev, "unable to get imx sema4 resource 0\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ imx6_sema4->ioaddr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(imx6_sema4->ioaddr)) {
+ ret = PTR_ERR(imx6_sema4->ioaddr);
+ goto err;
+ }
+
+ imx6_sema4->irq = platform_get_irq(pdev, 0);
+ if (!imx6_sema4->irq) {
+ dev_err(&pdev->dev, "failed to get irq\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = devm_request_irq(&pdev->dev, imx6_sema4->irq, imx_sema4_isr,
+ IRQF_SHARED, "imx6sx-sema4", imx6_sema4);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request imx sema4 irq\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, imx6_sema4);
+
+err:
+ return ret;
+}
+
+static int imx_sema4_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct platform_driver imx_sema4_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imx-sema4",
+ .of_match_table = imx_sema4_dt_ids,
+ },
+ .probe = imx_sema4_probe,
+ .remove = imx_sema4_remove,
+};
+
+static int __init imx_sema4_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_sema4_driver);
+ if (ret)
+ pr_err("Unable to initialize sema4 driver\n");
+ else
+ pr_info("imx sema4 driver is registered.\n");
+
+ return ret;
+}
+
+static void __exit imx_sema4_exit(void)
+{
+ pr_info("imx sema4 driver is unregistered.\n");
+ platform_driver_unregister(&imx_sema4_driver);
+}
+
+module_exit(imx_sema4_exit);
+module_init(imx_sema4_init);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX SEMA4 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index a905796f7f85..211a4fce638b 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -114,6 +114,14 @@ config ARM_IMX_CPUFREQ_DT
If in doubt, say N.
+config ARM_IMX7ULP_CPUFREQ
+ tristate "NXP i.MX7ULP cpufreq support"
+ depends on ARCH_MXC
+ help
+ This adds cpufreq driver support for NXP i.MX7ULP series SoCs.
+
+ If in doubt, say N.
+
config ARM_KIRKWOOD_CPUFREQ
def_bool MACH_KIRKWOOD
help
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 9a9f5ccd13d9..f6fdc4e4014c 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -57,6 +57,7 @@ obj-$(CONFIG_ARCH_DAVINCI) += davinci-cpufreq.o
obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o
obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o
obj-$(CONFIG_ARM_IMX_CPUFREQ_DT) += imx-cpufreq-dt.o
+obj-$(CONFIG_ARM_IMX7ULP_CPUFREQ) += imx7ulp-cpufreq.o
obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o
obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ) += mediatek-cpufreq.o
obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o
diff --git a/drivers/cpufreq/imx-cpufreq-dt.c b/drivers/cpufreq/imx-cpufreq-dt.c
index 35db14cf3102..26531f0004d6 100644
--- a/drivers/cpufreq/imx-cpufreq-dt.c
+++ b/drivers/cpufreq/imx-cpufreq-dt.c
@@ -44,19 +44,19 @@ static int imx_cpufreq_dt_probe(struct platform_device *pdev)
mkt_segment = (cell_value & OCOTP_CFG3_MKT_SEGMENT_MASK) >> OCOTP_CFG3_MKT_SEGMENT_SHIFT;
/*
- * Early samples without fuses written report "0 0" which means
- * consumer segment and minimum speed grading.
- *
- * According to datasheet minimum speed grading is not supported for
- * consumer parts so clamp to 1 to avoid warning for "no OPPs"
+ * Early samples without fuses written report "0 0" which may NOT
+ * match any OPP defined in DT. So clamp to minimum OPP defined in
+ * DT to avoid warning for "no OPPs".
*
* Applies to i.MX8M series SoCs.
*/
- if (mkt_segment == 0 && speed_grade == 0 && (
- of_machine_is_compatible("fsl,imx8mm") ||
- of_machine_is_compatible("fsl,imx8mn") ||
- of_machine_is_compatible("fsl,imx8mq")))
- speed_grade = 1;
+ if (mkt_segment == 0 && speed_grade == 0) {
+ if (of_machine_is_compatible("fsl,imx8mm") ||
+ of_machine_is_compatible("fsl,imx8mq"))
+ speed_grade = 1;
+ if (of_machine_is_compatible("fsl,imx8mn"))
+ speed_grade = 0xb;
+ }
supported_hw[0] = BIT(speed_grade);
supported_hw[1] = BIT(mkt_segment);
diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c
index 648a09a1778a..a03eee9bed97 100644
--- a/drivers/cpufreq/imx6q-cpufreq.c
+++ b/drivers/cpufreq/imx6q-cpufreq.c
@@ -3,6 +3,7 @@
* Copyright (C) 2013 Freescale Semiconductor, Inc.
*/
+#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
@@ -14,14 +15,27 @@
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
+#include <linux/suspend.h>
#define PU_SOC_VOLTAGE_NORMAL 1250000
#define PU_SOC_VOLTAGE_HIGH 1275000
#define FREQ_1P2_GHZ 1200000000
+#define FREQ_396_MHZ 396000
-static struct regulator *arm_reg;
-static struct regulator *pu_reg;
-static struct regulator *soc_reg;
+#define PU_SOC_VOLTAGE_NORMAL 1250000
+#define PU_SOC_VOLTAGE_HIGH 1275000
+#define DC_VOLTAGE_MIN 1300000
+#define DC_VOLTAGE_MAX 1400000
+#define FREQ_1P2_GHZ 1200000000
+#define FREQ_396_MHZ 396000
+#define FREQ_528_MHZ 528000
+#define FREQ_198_MHZ 198000
+#define FREQ_24_MHZ 24000
+
+struct regulator *arm_reg;
+struct regulator *pu_reg;
+struct regulator *soc_reg;
+struct regulator *dc_reg;
enum IMX6_CPUFREQ_CLKS {
ARM,
@@ -29,12 +43,15 @@ enum IMX6_CPUFREQ_CLKS {
STEP,
PLL1_SW,
PLL2_PFD2_396M,
+ PLL1,
+ PLL1_BYPASS,
+ PLL1_BYPASS_SRC,
/* MX6UL requires two more clks */
PLL2_BUS,
SECONDARY_SEL,
};
-#define IMX6Q_CPUFREQ_CLK_NUM 5
-#define IMX6UL_CPUFREQ_CLK_NUM 7
+#define IMX6Q_CPUFREQ_CLK_NUM 8
+#define IMX6UL_CPUFREQ_CLK_NUM 10
static int num_clks;
static struct clk_bulk_data clks[] = {
@@ -43,6 +60,9 @@ static struct clk_bulk_data clks[] = {
{ .id = "step" },
{ .id = "pll1_sw" },
{ .id = "pll2_pfd2_396m" },
+ { .id = "pll1" },
+ { .id = "pll1_bypass" },
+ { .id = "pll1_bypass_src" },
{ .id = "pll2_bus" },
{ .id = "secondary_sel" },
};
@@ -56,6 +76,9 @@ static unsigned int transition_latency;
static u32 *imx6_soc_volt;
static u32 soc_opp_count;
+static bool ignore_dc_reg;
+static bool low_power_run_support;
+
static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
{
struct dev_pm_opp *opp;
@@ -66,7 +89,16 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
new_freq = freq_table[index].frequency;
freq_hz = new_freq * 1000;
- old_freq = clk_get_rate(clks[ARM].clk) / 1000;
+ old_freq = policy->cur;
+
+ /*
+ * ON i.MX6ULL, the 24MHz setpoint is not seen by cpufreq
+ * so we neet to prevent the cpufreq change frequency
+ * from 24MHz to 198Mhz directly. busfreq will handle this
+ * when exit from low bus mode.
+ */
+ if (old_freq == FREQ_24_MHZ && new_freq == FREQ_198_MHZ)
+ return 0;
opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
if (IS_ERR(opp)) {
@@ -83,6 +115,13 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
old_freq / 1000, volt_old / 1000,
new_freq / 1000, volt / 1000);
+ if (low_power_run_support) {
+ if (old_freq == freq_table[0].frequency)
+ request_bus_freq(BUS_FREQ_HIGH);
+ } else if (old_freq <= FREQ_396_MHZ && new_freq > FREQ_396_MHZ) {
+ request_bus_freq(BUS_FREQ_HIGH);
+ }
+
/* scaling up? scale voltage before frequency */
if (new_freq > old_freq) {
if (!IS_ERR(pu_reg)) {
@@ -143,11 +182,18 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
clk_set_parent(clks[STEP].clk, clks[PLL2_PFD2_396M].clk);
clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk);
if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk)) {
+ /* Ensure that pll1_bypass is set back to
+ * pll1. We have to do this first so that the
+ * change rate done to pll1_sys_clk done below
+ * can propagate up to pll1.
+ */
+ clk_set_parent(clks[PLL1_BYPASS].clk, clks[PLL1].clk);
clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000);
clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
} else {
/* pll1_sys needs to be enabled for divider rate change to work. */
pll1_sys_temp_enabled = true;
+ clk_set_parent(clks[PLL1_BYPASS].clk, clks[PLL1_BYPASS_SRC].clk);
clk_prepare_enable(clks[PLL1_SYS].clk);
}
}
@@ -185,16 +231,34 @@ static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
}
}
+ /*
+ * If CPU is dropped to the lowest level, release the need
+ * for a high bus frequency.
+ */
+ if (low_power_run_support) {
+ if (new_freq == freq_table[0].frequency)
+ release_bus_freq(BUS_FREQ_HIGH);
+ } else if (old_freq > FREQ_396_MHZ && new_freq <= FREQ_396_MHZ) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ }
+
return 0;
}
static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
{
policy->clk = clks[ARM].clk;
+ policy->cur = clk_get_rate(policy->clk) / 1000;
cpufreq_generic_init(policy, freq_table, transition_latency);
policy->suspend_freq = max_freq;
dev_pm_opp_of_register_em(policy->cpus);
+ if (low_power_run_support && policy->cur > freq_table[0].frequency) {
+ request_bus_freq(BUS_FREQ_HIGH);
+ } else if (policy->cur > FREQ_396_MHZ) {
+ request_bus_freq(BUS_FREQ_HIGH);
+ }
+
return 0;
}
@@ -324,6 +388,43 @@ static int imx6ul_opp_check_speed_grading(struct device *dev)
return ret;
}
+static int imx6_cpufreq_pm_notify(struct notifier_block *nb,
+ unsigned long event, void *dummy)
+{
+ int ret;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
+ ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MAX, 0);
+ if (ret) {
+ dev_err(cpu_dev,
+ "failed to scale dc_reg to max: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+ case PM_POST_SUSPEND:
+ if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
+ ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MIN, 0);
+ if (ret) {
+ dev_err(cpu_dev,
+ "failed to scale dc_reg to min: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block imx6_cpufreq_pm_notifier = {
+ .notifier_call = imx6_cpufreq_pm_notify,
+};
+
static int imx6q_cpufreq_probe(struct platform_device *pdev)
{
struct device_node *np;
@@ -372,6 +473,11 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)
goto put_reg;
}
+ dc_reg = devm_regulator_get_optional(cpu_dev, "dc");
+
+ /* On i.MX6ULL, check the 24MHz low power run mode support */
+ low_power_run_support = of_property_read_bool(np, "fsl,low-power-run");
+
ret = dev_pm_opp_of_add_table(cpu_dev);
if (ret < 0) {
dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
@@ -382,12 +488,14 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)
of_machine_is_compatible("fsl,imx6ull")) {
ret = imx6ul_opp_check_speed_grading(cpu_dev);
if (ret) {
- if (ret == -EPROBE_DEFER)
- goto put_node;
+ if (ret == -EPROBE_DEFER) {
+ dev_err(cpu_dev, "defer the cpufreq\n\n");
+ goto out_free_opp;
+ }
dev_err(cpu_dev, "failed to read ocotp: %d\n",
ret);
- goto put_node;
+ goto out_free_opp;
}
} else {
imx6q_opp_check_speed_grading(cpu_dev);
@@ -408,6 +516,21 @@ static int imx6q_cpufreq_probe(struct platform_device *pdev)
goto out_free_opp;
}
+ /*
+ * On i.MX6UL/ULL EVK board, if the SOC is run in overide frequency,
+ * the dc_regulator voltage should not be touched.
+ */
+ if (freq_table[num - 1].frequency > FREQ_528_MHZ)
+ ignore_dc_reg = true;
+ if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
+ ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MIN, 0);
+ if (ret) {
+ dev_err(cpu_dev,
+ "failed to scale dc_reg to min: %d\n", ret);
+ return ret;
+ }
+ }
+
/* Make imx6_soc_volt array's size same as arm opp number */
imx6_soc_volt = devm_kcalloc(cpu_dev, num, sizeof(*imx6_soc_volt),
GFP_KERNEL);
@@ -490,6 +613,8 @@ soc_opp_out:
goto free_freq_table;
}
+ register_pm_notifier(&imx6_cpufreq_pm_notifier);
+
of_node_put(np);
return 0;
diff --git a/drivers/cpufreq/imx7ulp-cpufreq.c b/drivers/cpufreq/imx7ulp-cpufreq.c
new file mode 100644
index 000000000000..43a9b9520ae9
--- /dev/null
+++ b/drivers/cpufreq/imx7ulp-cpufreq.c
@@ -0,0 +1,260 @@
+ /*
+ * Copyright 2017 NXP.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/suspend.h>
+
+#define MAX_RUN_FREQ 528000
+
+static struct clk *arm_clk;
+static struct clk *core_div;
+static struct clk *sys_sel;
+static struct clk *hsrun_sys_sel;
+static struct clk *hsrun_core;
+static struct clk *spll_pfd0;
+static struct clk *spll_sel;
+static struct clk *firc_clk;
+static struct clk *spll;
+
+static struct pm_qos_request pm_qos_hsrun;
+static struct regulator *arm_reg;
+static struct device *cpu_dev;
+static struct cpufreq_frequency_table *freq_table;
+static unsigned int transition_latency;
+
+static int imx7ulp_set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+ struct dev_pm_opp *opp;
+ unsigned long freq_hz, volt, volt_old;
+ unsigned int old_freq, new_freq;
+ int ret;
+
+ new_freq = freq_table[index].frequency;
+ freq_hz = new_freq * 1000;
+ old_freq = clk_get_rate(arm_clk) / 1000;
+ if (new_freq == 0 || old_freq == 0)
+ return -EINVAL;
+
+ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
+ if (IS_ERR(opp)) {
+ dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);
+ return PTR_ERR(opp);
+ }
+ volt = dev_pm_opp_get_voltage(opp);
+ dev_pm_opp_put(opp);
+
+ volt_old = regulator_get_voltage(arm_reg);
+
+ dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n",
+ old_freq / 1000, volt_old / 1000,
+ new_freq / 1000, volt / 1000);
+
+ /* Scaling up? scale voltage before frequency */
+ if (new_freq > old_freq) {
+ ret = regulator_set_voltage_tol(arm_reg, volt, 0);
+ if (ret) {
+ dev_err(cpu_dev, "failed to scale vddarm up: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* before changing pll_arm rate, change the arm_src's soure
+ * to firc clk first.
+ */
+ if (new_freq > MAX_RUN_FREQ) {
+ pm_qos_add_request(&pm_qos_hsrun, PM_QOS_CPU_DMA_LATENCY, 0);
+ /* change the RUN clock to firc */
+ clk_set_parent(sys_sel, firc_clk);
+ /* change the clock rate in HSRUN */
+ clk_set_rate(spll, 480000000);
+ clk_set_rate(spll_pfd0, new_freq * 1000);
+ clk_set_parent(hsrun_sys_sel, spll_sel);
+ clk_set_parent(arm_clk, hsrun_core);
+ } else {
+ /* change the HSRUN clock to firc */
+ clk_set_parent(hsrun_sys_sel, firc_clk);
+ /* change the clock rate in RUN */
+ clk_set_rate(spll, 528000000);
+ clk_set_rate(spll_pfd0, new_freq * 1000);
+ clk_set_parent(sys_sel, spll_sel);
+ clk_set_parent(arm_clk, core_div);
+ if (old_freq > MAX_RUN_FREQ)
+ pm_qos_remove_request(&pm_qos_hsrun);
+ }
+
+ /* scaling down? scaling voltage after frequency */
+ if (new_freq < old_freq) {
+ ret = regulator_set_voltage_tol(arm_reg, volt, 0);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to scale vddarm down: %d\n", ret);
+ ret = 0;
+ }
+ }
+
+ return 0;
+}
+
+static int imx7ulp_cpufreq_init(struct cpufreq_policy *policy)
+{
+ policy->clk = arm_clk;
+ policy->cur = clk_get_rate(arm_clk) / 1000;
+ policy->suspend_freq = freq_table[0].frequency;
+
+ cpufreq_generic_init(policy, freq_table, transition_latency);
+
+ return 0;
+}
+
+static struct cpufreq_driver imx7ulp_cpufreq_driver = {
+ .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .target_index = imx7ulp_set_target,
+ .get = cpufreq_generic_get,
+ .init = imx7ulp_cpufreq_init,
+ .name = "imx7ulp-cpufreq",
+ .attr = cpufreq_generic_attr,
+#ifdef CONFIG_PM
+ .suspend = cpufreq_generic_suspend,
+#endif
+};
+
+static int imx7ulp_cpufreq_probe(struct platform_device *pdev)
+{
+ struct device_node *np;
+ int ret;
+
+ cpu_dev = get_cpu_device(0);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu0 device\n");
+ return -ENOENT;
+ }
+
+ np = of_node_get(cpu_dev->of_node);
+ if (!np) {
+ dev_err(cpu_dev, "failed to find the cpu0 node\n");
+ return -ENOENT;
+ }
+
+ arm_clk = clk_get(cpu_dev, "arm");
+ sys_sel = clk_get(cpu_dev, "sys_sel");
+ core_div = clk_get(cpu_dev, "core_div");
+ hsrun_sys_sel = clk_get(cpu_dev, "hsrun_sys_sel");
+ hsrun_core = clk_get(cpu_dev, "hsrun_core");
+ spll_pfd0 = clk_get(cpu_dev, "spll_pfd0");
+ spll_sel = clk_get(cpu_dev, "spll_sel");
+ firc_clk = clk_get(cpu_dev, "firc");
+ spll = clk_get(cpu_dev, "spll");
+
+ if (IS_ERR(arm_clk) || IS_ERR(sys_sel) || IS_ERR(spll_sel) ||
+ IS_ERR(spll_sel) || IS_ERR(firc_clk) || IS_ERR(hsrun_sys_sel) ||
+ IS_ERR(hsrun_core) || IS_ERR(spll)) {
+ dev_err(cpu_dev, "failed to get cpu clock\n");
+ ret = -ENOENT;
+ goto put_clk;
+ }
+
+ arm_reg = regulator_get(cpu_dev, "arm");
+ if (IS_ERR(arm_reg)) {
+ dev_err(cpu_dev, "failed to get regulator\n");
+ ret = -ENOENT;
+ goto put_reg;
+ }
+
+ ret = dev_pm_opp_of_add_table(cpu_dev);
+ if (ret < 0) {
+ dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
+ goto put_reg;
+ }
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table\n");
+ goto put_reg;
+ }
+
+ if (of_property_read_u32(np, "clock-latency", &transition_latency))
+ transition_latency = CPUFREQ_ETERNAL;
+
+ ret = cpufreq_register_driver(&imx7ulp_cpufreq_driver);
+ if (ret) {
+ dev_err(cpu_dev, "failed to register driver\n");
+ goto free_opp_table;
+ }
+
+ return 0;
+
+free_opp_table:
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+put_reg:
+ regulator_put(arm_reg);
+put_clk:
+ if (!IS_ERR(arm_clk))
+ clk_put(arm_clk);
+ if (!IS_ERR(sys_sel))
+ clk_put(sys_sel);
+ if (!IS_ERR(core_div))
+ clk_put(core_div);
+ if (!IS_ERR(hsrun_sys_sel))
+ clk_put(hsrun_sys_sel);
+ if (!IS_ERR(hsrun_core))
+ clk_put(hsrun_core);
+ if (!IS_ERR(spll_pfd0))
+ clk_put(spll_pfd0);
+ if (!IS_ERR(spll_sel))
+ clk_put(spll_sel);
+ if (!IS_ERR(firc_clk))
+ clk_put(firc_clk);
+ if (!IS_ERR(spll))
+ clk_put(spll);
+
+ return ret;
+}
+
+static int imx7ulp_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&imx7ulp_cpufreq_driver);
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+
+ regulator_put(arm_reg);
+ clk_put(arm_clk);
+ clk_put(sys_sel);
+ clk_put(core_div);
+ clk_put(hsrun_sys_sel);
+ clk_put(hsrun_core);
+ clk_put(spll_pfd0);
+ clk_put(spll_sel);
+ clk_put(firc_clk);
+ clk_put(spll);
+
+ return 0;
+}
+
+static struct platform_driver imx7ulp_cpufreq_platdrv = {
+ .driver = {
+ .name = "imx7ulp-cpufreq",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx7ulp_cpufreq_probe,
+ .remove = imx7ulp_cpufreq_remove,
+};
+
+module_platform_driver(imx7ulp_cpufreq_platdrv);
+
+MODULE_DESCRIPTION("NXP i.MX7ULP cpufreq driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/firmware/imx/scu-pd.c b/drivers/firmware/imx/scu-pd.c
index 0ff12174eea9..ae062bb00977 100644..100755
--- a/drivers/firmware/imx/scu-pd.c
+++ b/drivers/firmware/imx/scu-pd.c
@@ -44,10 +44,13 @@
*
*/
+#include <linux/arm-smccc.h>
#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/console.h>
#include <linux/firmware/imx/sci.h>
#include <linux/firmware/imx/svc/rm.h>
#include <linux/io.h>
+#include <linux/irqchip/arm-gic-v3.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -56,6 +59,17 @@
#include <linux/pm.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
+#include <linux/syscore_ops.h>
+
+#define IMX_WU_MAX_IRQS (((IMX_SC_R_LAST + 31) / 32 ) * 32 )
+
+#define IMX_SIP_WAKEUP_SRC 0xc2000009
+#define IMX_SIP_WAKEUP_SRC_SCU 0x1
+#define IMX_SIP_WAKEUP_SRC_IRQSTEER 0x2
+
+static u32 wu[IMX_WU_MAX_IRQS];
+static int wu_num;
+static void __iomem *gic_dist_base;
/* SCU Power Mode Protocol definition */
struct imx_sc_msg_req_set_resource_power_mode {
@@ -86,6 +100,8 @@ struct imx_sc_pd_soc {
u8 num_ranges;
};
+int imx_con_rsrc;
+
static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
/* LSIO SS */
{ "pwm", IMX_SC_R_PWM_0, 8, true, 0 },
@@ -110,14 +126,26 @@ static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
{ "audio-pll0", IMX_SC_R_AUDIO_PLL_0, 1, false, 0 },
{ "audio-pll1", IMX_SC_R_AUDIO_PLL_1, 1, false, 0 },
{ "audio-clk-0", IMX_SC_R_AUDIO_CLK_0, 1, false, 0 },
- { "dma0-ch", IMX_SC_R_DMA_0_CH0, 16, true, 0 },
+ { "audio-clk-1", IMX_SC_R_AUDIO_CLK_1, 1, false, 0 },
+ { "mclk-out-0", IMX_SC_R_MCLK_OUT_0, 1, false, 0 },
+ { "mclk-out-1", IMX_SC_R_MCLK_OUT_1, 1, false, 0 },
+ { "dma0-ch", IMX_SC_R_DMA_0_CH0, 32, true, 0 },
{ "dma1-ch", IMX_SC_R_DMA_1_CH0, 16, true, 0 },
- { "dma2-ch", IMX_SC_R_DMA_2_CH0, 5, true, 0 },
+ { "dma2-ch-0", IMX_SC_R_DMA_2_CH0, 5, true, 0 },
+ { "dma2-ch-1", IMX_SC_R_DMA_2_CH5, 27, true, 0 },
+ { "dma3-ch", IMX_SC_R_DMA_3_CH0, 32, true, 0 },
{ "asrc0", IMX_SC_R_ASRC_0, 1, false, 0 },
{ "asrc1", IMX_SC_R_ASRC_1, 1, false, 0 },
{ "esai0", IMX_SC_R_ESAI_0, 1, false, 0 },
+ { "esai1", IMX_SC_R_ESAI_1, 1, false, 0 },
{ "spdif0", IMX_SC_R_SPDIF_0, 1, false, 0 },
+ { "spdif1", IMX_SC_R_SPDIF_1, 1, false, 0 },
{ "sai", IMX_SC_R_SAI_0, 3, true, 0 },
+ { "sai3", IMX_SC_R_SAI_3, 1, false, 0 },
+ { "sai4", IMX_SC_R_SAI_4, 1, false, 0 },
+ { "sai5", IMX_SC_R_SAI_5, 1, false, 0 },
+ { "sai6", IMX_SC_R_SAI_6, 1, false, 0 },
+ { "sai7", IMX_SC_R_SAI_7, 1, false, 0 },
{ "amix", IMX_SC_R_AMIX, 1, false, 0 },
{ "mqs0", IMX_SC_R_MQS_0, 1, false, 0 },
{ "dsp", IMX_SC_R_DSP, 1, false, 0 },
@@ -131,6 +159,7 @@ static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
{ "lcd", IMX_SC_R_LCD_0, 1, true, 0 },
{ "lcd0-pwm", IMX_SC_R_LCD_0_PWM_0, 1, true, 0 },
{ "lpuart", IMX_SC_R_UART_0, 4, true, 0 },
+ { "sim", IMX_SC_R_EMVSIM_0, 2, true, 0 },
{ "lpspi", IMX_SC_R_SPI_0, 4, true, 0 },
{ "irqstr_dsp", IMX_SC_R_IRQSTR_DSP, 1, false, 0 },
@@ -139,13 +168,22 @@ static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
{ "vpu-pid", IMX_SC_R_VPU_PID0, 8, true, 0 },
{ "vpu-dec0", IMX_SC_R_VPU_DEC_0, 1, false, 0 },
{ "vpu-enc0", IMX_SC_R_VPU_ENC_0, 1, false, 0 },
+ { "vpu-enc1", IMX_SC_R_VPU_ENC_1, 1, false, 0 },
+ { "vpu-mu0", IMX_SC_R_VPU_MU_0, 1, false, 0 },
+ { "vpu-mu1", IMX_SC_R_VPU_MU_1, 1, false, 0 },
+ { "vpu-mu2", IMX_SC_R_VPU_MU_2, 1, false, 0 },
/* GPU SS */
{ "gpu0-pid", IMX_SC_R_GPU_0_PID0, 4, true, 0 },
+ { "gpu1-pid", IMX_SC_R_GPU_1_PID0, 4, true, 0 },
+
/* HSIO SS */
+ { "pcie-a", IMX_SC_R_PCIE_A, 1, false, 0 },
+ { "serdes-0", IMX_SC_R_SERDES_0, 1, false, 0 },
{ "pcie-b", IMX_SC_R_PCIE_B, 1, false, 0 },
{ "serdes-1", IMX_SC_R_SERDES_1, 1, false, 0 },
+ { "sata-0", IMX_SC_R_SATA_0, 1, false, 0 },
{ "hsio-gpio", IMX_SC_R_HSIO_GPIO, 1, false, 0 },
/* MIPI SS */
@@ -153,8 +191,18 @@ static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
{ "mipi0-pwm0", IMX_SC_R_MIPI_0_PWM_0, 1, false, 0 },
{ "mipi0-i2c", IMX_SC_R_MIPI_0_I2C_0, 2, true, 0 },
+ { "mipi1", IMX_SC_R_MIPI_1, 1, 0 },
+ { "mipi1-pwm0", IMX_SC_R_MIPI_1_PWM_0, 1, 0 },
+ { "mipi1-i2c", IMX_SC_R_MIPI_1_I2C_0, 2, 1 },
+
/* LVDS SS */
{ "lvds0", IMX_SC_R_LVDS_0, 1, false, 0 },
+ { "lvds0-i2c0", IMX_SC_R_LVDS_0_I2C_0, 1, false, 0 },
+ { "lvds0-pwm0", IMX_SC_R_LVDS_0_PWM_0, 1, false, 0 },
+
+ { "lvds1", IMX_SC_R_LVDS_1, 1, false, 0 },
+ { "lvds1-i2c0", IMX_SC_R_LVDS_1_I2C_0, 1, false, 0 },
+ { "lvds1-pwm0", IMX_SC_R_LVDS_1_PWM_0, 1, false, 0 },
{ "mipi1", IMX_SC_R_MIPI_1, 1, 0 },
{ "mipi1-pwm0", IMX_SC_R_MIPI_1_PWM_0, 1, 0 },
@@ -164,6 +212,49 @@ static const struct imx_sc_pd_range imx8qxp_scu_pd_ranges[] = {
/* DC SS */
{ "dc0", IMX_SC_R_DC_0, 1, false, 0 },
{ "dc0-pll", IMX_SC_R_DC_0_PLL_0, 2, true, 0 },
+ { "dc0-video", IMX_SC_R_DC_0_VIDEO0, 2, true, 0 },
+
+ { "dc1", IMX_SC_R_DC_1, 1, false, 0 },
+ { "dc1-pll", IMX_SC_R_DC_1_PLL_0, 2, true, 0 },
+ { "dc1-video", IMX_SC_R_DC_1_VIDEO0, 2, true, 0 },
+
+ /* CM40 SS */
+ { "cm40_i2c", IMX_SC_R_M4_0_I2C, 1, false, 0 },
+ { "cm40_intmux", IMX_SC_R_M4_0_INTMUX, 1, false, 0 },
+
+ /* CM41 SS */
+ { "cm41_i2c", IMX_SC_R_M4_1_I2C, 1, false, 0 },
+ { "cm41_intmux", IMX_SC_R_M4_1_INTMUX, 1, false, 0 },
+
+ /* IMAGE SS */
+ { "img-pdma", IMX_SC_R_ISI_CH0, 8, true, 0 },
+ { "img-csi0", IMX_SC_R_CSI_0, 1, false, 0 },
+ { "img-csi0-i2c0", IMX_SC_R_CSI_0_I2C_0, 1, false, 0 },
+ { "img-csi0-pwm0", IMX_SC_R_CSI_0_PWM_0, 1, false, 0 },
+ { "img-csi1", IMX_SC_R_CSI_1, 1, false, 0 },
+ { "img-csi1-i2c0", IMX_SC_R_CSI_1_I2C_0, 1, false, 0 },
+ { "img-csi1-pwm0", IMX_SC_R_CSI_1_PWM_0, 1, false, 0 },
+ { "img-parallel", IMX_SC_R_PI_0, 1, false, 0 },
+ { "img-parallel-i2c0", IMX_SC_R_PI_0_I2C_0, 1, false, 0 },
+ { "img-parallel-pwm0", IMX_SC_R_PI_0_PWM_0, 2, true, 0 },
+ { "img-parallel-pll", IMX_SC_R_PI_0_PLL, 1, false, 0 },
+ { "img-jpegdec-mp", IMX_SC_R_MJPEG_DEC_MP, 1, false, 0 },
+ { "img-jpegdec-s0", IMX_SC_R_MJPEG_DEC_S0, 4, true, 0 },
+ { "img-jpegenc-mp", IMX_SC_R_MJPEG_ENC_MP, 1, false, 0 },
+ { "img-jpegenc-s0", IMX_SC_R_MJPEG_ENC_S0, 4, true, 0 },
+
+ /* HDMI TX SS */
+ { "hdmi-tx", IMX_SC_R_HDMI, 1, false, 0},
+ { "hdmi-tx-i2s", IMX_SC_R_HDMI_I2S, 1, false, 0},
+ { "hdmi-tx-i2c0", IMX_SC_R_HDMI_I2C_0, 1, false, 0},
+ { "hdmi-tx-pll0", IMX_SC_R_HDMI_PLL_0, 1, false, 0},
+ { "hdmi-tx-pll1", IMX_SC_R_HDMI_PLL_1, 1, false, 0},
+
+ /* SECURITY SS */
+ { "sec-jr", IMX_SC_R_CAAM_JR2, 2, true, 2},
+
+ /* BOARD SS */
+ { "board", IMX_SC_R_BOARD_R0, 8, true, 0},
};
static const struct imx_sc_pd_soc imx8qxp_scu_pd = {
@@ -179,6 +270,73 @@ to_imx_sc_pd(struct generic_pm_domain *genpd)
return container_of(genpd, struct imx_sc_pm_domain, pd);
}
+static int imx_pm_domains_suspend(void)
+{
+ struct arm_smccc_res res;
+ u32 offset;
+ int i;
+
+ for (i = 0; i < wu_num; i++) {
+ offset = GICD_ISENABLER + ((wu[i] + 32) / 32) * 4;
+ if (BIT(wu[i] % 32) & readl_relaxed(gic_dist_base + offset)) {
+ arm_smccc_smc(IMX_SIP_WAKEUP_SRC,
+ IMX_SIP_WAKEUP_SRC_IRQSTEER,
+ 0, 0, 0, 0, 0, 0, &res);
+ return 0;
+ }
+ }
+
+ arm_smccc_smc(IMX_SIP_WAKEUP_SRC,
+ IMX_SIP_WAKEUP_SRC_SCU,
+ 0, 0, 0, 0, 0, 0, &res);
+
+ return 0;
+}
+
+struct syscore_ops imx_pm_domains_syscore_ops = {
+ .suspend = imx_pm_domains_suspend,
+};
+
+static void imx_sc_pd_enable_irqsteer_wakeup(struct device_node *np)
+{
+ struct device_node *gic_node;
+ unsigned int i;
+
+ wu_num = of_property_count_u32_elems(np, "wakeup-irq");
+ if (wu_num <= 0) {
+ pr_warn("no irqsteer wakeup source supported!\n");
+ return;
+ }
+
+ gic_node = of_find_compatible_node(NULL, NULL, "arm,gic-v3");
+ WARN_ON(!gic_node);
+
+ gic_dist_base = of_iomap(gic_node, 0);
+ WARN_ON(!gic_dist_base);
+
+ for (i = 0; i < wu_num; i++)
+ WARN_ON(of_property_read_u32_index(np, "wakeup-irq", i, &wu[i]));
+
+ register_syscore_ops(&imx_pm_domains_syscore_ops);
+}
+
+static void imx_sc_pd_get_console_rsrc(void)
+{
+ struct of_phandle_args specs;
+ int ret;
+
+ if (!of_stdout)
+ return;
+
+ ret = of_parse_phandle_with_args(of_stdout, "power-domains",
+ "#power-domain-cells",
+ 0, &specs);
+ if (ret)
+ return;
+
+ imx_con_rsrc = specs.args[0];
+}
+
static int imx_sc_pd_power(struct generic_pm_domain *domain, bool power_on)
{
struct imx_sc_msg_req_set_resource_power_mode msg;
@@ -194,7 +352,12 @@ static int imx_sc_pd_power(struct generic_pm_domain *domain, bool power_on)
hdr->size = 2;
msg.resource = pd->rsrc;
- msg.mode = power_on ? IMX_SC_PM_PW_MODE_ON : IMX_SC_PM_PW_MODE_LP;
+ msg.mode = power_on ? IMX_SC_PM_PW_MODE_ON : pd->pd.state_idx ?
+ IMX_SC_PM_PW_MODE_OFF : IMX_SC_PM_PW_MODE_LP;
+
+ /* keep uart console power on for no_console_suspend */
+ if (imx_con_rsrc == pd->rsrc && !console_suspend_enabled && !power_on)
+ return 0;
ret = imx_scu_call_rpc(pm_ipc_handle, &msg, true);
if (ret)
@@ -247,6 +410,8 @@ imx_scu_add_pm_domain(struct device *dev, int idx,
const struct imx_sc_pd_range *pd_ranges)
{
struct imx_sc_pm_domain *sc_pd;
+ struct genpd_power_state *states;
+ bool is_off = true;
int ret;
if (!imx_sc_rm_is_resource_owned(pm_ipc_handle, pd_ranges->rsrc + idx))
@@ -256,9 +421,23 @@ imx_scu_add_pm_domain(struct device *dev, int idx,
if (!sc_pd)
return ERR_PTR(-ENOMEM);
+ states = devm_kcalloc(dev, 2, sizeof(*states), GFP_KERNEL);
+ if (!states) {
+ devm_kfree(dev, sc_pd);
+ return ERR_PTR(-ENOMEM);
+ }
+
sc_pd->rsrc = pd_ranges->rsrc + idx;
sc_pd->pd.power_off = imx_sc_pd_power_off;
sc_pd->pd.power_on = imx_sc_pd_power_on;
+ sc_pd->pd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
+ states[0].power_off_latency_ns = 25000;
+ states[0].power_on_latency_ns = 25000;
+ states[1].power_off_latency_ns = 2500000;
+ states[1].power_on_latency_ns = 2500000;
+
+ sc_pd->pd.states = states;
+ sc_pd->pd.state_count = 2;
if (pd_ranges->postfix)
snprintf(sc_pd->name, sizeof(sc_pd->name),
@@ -268,20 +447,26 @@ imx_scu_add_pm_domain(struct device *dev, int idx,
"%s", pd_ranges->name);
sc_pd->pd.name = sc_pd->name;
+ if (imx_con_rsrc == sc_pd->rsrc) {
+ sc_pd->pd.flags |= GENPD_FLAG_RPM_ALWAYS_ON;
+ is_off = false;
+ }
if (sc_pd->rsrc >= IMX_SC_R_LAST) {
dev_warn(dev, "invalid pd %s rsrc id %d found",
sc_pd->name, sc_pd->rsrc);
devm_kfree(dev, sc_pd);
+ devm_kfree(dev, states);
return NULL;
}
- ret = pm_genpd_init(&sc_pd->pd, NULL, true);
+ ret = pm_genpd_init(&sc_pd->pd, NULL, is_off);
if (ret) {
dev_warn(dev, "failed to init pd %s rsrc id %d",
sc_pd->name, sc_pd->rsrc);
devm_kfree(dev, sc_pd);
+ devm_kfree(dev, states);
return NULL;
}
@@ -343,6 +528,9 @@ static int imx_sc_pd_probe(struct platform_device *pdev)
if (!pd_soc)
return -ENODEV;
+ imx_sc_pd_get_console_rsrc();
+ imx_sc_pd_enable_irqsteer_wakeup(pdev->dev.of_node);
+
return imx_scu_init_pm_domains(&pdev->dev, pd_soc);
}
@@ -359,7 +547,12 @@ static struct platform_driver imx_sc_pd_driver = {
},
.probe = imx_sc_pd_probe,
};
-builtin_platform_driver(imx_sc_pd_driver);
+
+static int __init imx_sc_pd_driver_init(void)
+{
+ return platform_driver_register(&imx_sc_pd_driver);
+}
+subsys_initcall(imx_sc_pd_driver_init);
MODULE_AUTHOR("Dong Aisheng <aisheng.dong@nxp.com>");
MODULE_DESCRIPTION("IMX SCU Power Domain driver");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index c84a7b1caeb6..67cf7d28eed1 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -441,6 +441,14 @@ config CHARGER_ISP1704
Say Y to enable support for USB Charger Detection with
ISP1707/ISP1704 USB transceivers.
+config SABRESD_MAX8903
+ tristate "Sabresd Board Battery DC-DC Charger for USB and Adapter Power"
+ depends on TOUCHSCREEN_MAX11801
+ help
+ Say Y to enable support for the MAX8903 DC-DC charger and sysfs on
+ sabresd board.The driver supports controlling charger and battery
+ based on the status of charger connections with interrupt handlers.
+
config CHARGER_MAX8903
tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power"
help
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 6c7da920ea83..9e41cdb2eaff 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o
obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
+obj-$(CONFIG_SABRESD_MAX8903) += sabresd_battery.o
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
diff --git a/drivers/power/supply/sabresd_battery.c b/drivers/power/supply/sabresd_battery.c
new file mode 100644
index 000000000000..5f479f83525a
--- /dev/null
+++ b/drivers/power/supply/sabresd_battery.c
@@ -0,0 +1,1014 @@
+/*
+ * sabresd_battery.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2015 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.c
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/sabresd_battery.h>
+#include <linux/slab.h>
+
+#define BATTERY_UPDATE_INTERVAL 5 /*seconds*/
+#define LOW_VOLT_THRESHOLD 2800000
+#define HIGH_VOLT_THRESHOLD 4200000
+#define ADC_SAMPLE_COUNT 6
+
+struct max8903_data {
+ struct max8903_pdata *pdata;
+ struct device *dev;
+ struct power_supply *psy;
+ struct power_supply *usb;
+ struct power_supply *bat;
+ struct power_supply *detect_usb;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+ bool chg_state;
+ struct delayed_work work;
+ unsigned int interval;
+ unsigned short thermal_raw;
+ int voltage_uV;
+ int current_uA;
+ int battery_status;
+ int charger_online;
+ int charger_voltage_uV;
+ int real_capacity;
+ int percent;
+ int old_percent;
+ int usb_charger_online;
+ int first_delay_count;
+};
+
+typedef struct {
+ u32 voltage;
+ u32 percent;
+} battery_capacity , *pbattery_capacity;
+
+static int offset_discharger;
+static int offset_charger;
+static int offset_usb_charger;
+
+static battery_capacity chargingTable[] = {
+ {4050, 99},
+ {4040, 98},
+ {4020, 97},
+ {4010, 96},
+ {3990, 95},
+ {3980, 94},
+ {3970, 93},
+ {3960, 92},
+ {3950, 91},
+ {3940, 90},
+ {3930, 85},
+ {3920, 81},
+ {3910, 77},
+ {3900, 73},
+ {3890, 70},
+ {3860, 65},
+ {3830, 60},
+ {3780, 55},
+ {3760, 50},
+ {3740, 45},
+ {3720, 40},
+ {3700, 35},
+ {3680, 30},
+ {3660, 25},
+ {3640, 20},
+ {3620, 17},
+ {3600, 14},
+ {3580, 13},
+ {3560, 12},
+ {3540, 11},
+ {3520, 10},
+ {3500, 9},
+ {3480, 8},
+ {3460, 7},
+ {3440, 6},
+ {3430, 5},
+ {3420, 4},
+ {3020, 0},
+};
+
+static battery_capacity dischargingTable[] = {
+ {4050, 100},
+ {4035, 99},
+ {4020, 98},
+ {4010, 97},
+ {4000, 96},
+ {3990, 96},
+ {3980, 95},
+ {3970, 92},
+ {3960, 91},
+ {3950, 90},
+ {3940, 88},
+ {3930, 86},
+ {3920, 84},
+ {3910, 82},
+ {3900, 80},
+ {3890, 74},
+ {3860, 69},
+ {3830, 64},
+ {3780, 59},
+ {3760, 54},
+ {3740, 49},
+ {3720, 44},
+ {3700, 39},
+ {3680, 34},
+ {3660, 29},
+ {3640, 24},
+ {3620, 19},
+ {3600, 14},
+ {3580, 13},
+ {3560, 12},
+ {3540, 11},
+ {3520, 10},
+ {3500, 9},
+ {3480, 8},
+ {3460, 7},
+ {3440, 6},
+ {3430, 5},
+ {3420, 4},
+ {3020, 0},
+};
+
+u32 calibrate_battery_capability_percent(struct max8903_data *data)
+{
+ u8 i;
+ pbattery_capacity pTable;
+ u32 tableSize;
+
+ if (data->battery_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ pTable = dischargingTable;
+ tableSize = sizeof(dischargingTable)/
+ sizeof(dischargingTable[0]);
+ } else {
+ pTable = chargingTable;
+ tableSize = sizeof(chargingTable)/
+ sizeof(chargingTable[0]);
+ }
+ for (i = 0; i < tableSize; i++) {
+ if (data->voltage_uV >= pTable[i].voltage)
+ return pTable[i].percent;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property max8903_battery_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+extern int max11801_read_adc(void);
+
+static void max8903_charger_update_status(struct max8903_data *data)
+{
+ if (data->ta_in) {
+ data->charger_online = 1;
+ } else if (data->usb_in) {
+ data->usb_charger_online = 1;
+ } else {
+ data->charger_online = 0;
+ data->usb_charger_online = 0;
+ }
+
+ if (!data->charger_online && !data->usb_charger_online) {
+ data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if (gpio_get_value(data->pdata->chg) == 0) {
+ data->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if ((data->ta_in || data->usb_in) &&
+ gpio_get_value(data->pdata->chg) > 0) {
+ if (!data->pdata->feature_flag) {
+ if (data->percent >= 99)
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else
+ data->battery_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+}
+
+u32 calibration_voltage(struct max8903_data *data)
+{
+ u32 voltage_data = 0;
+ int adc_val = 0;
+ int i;
+ int offset;
+
+ if (!data->charger_online && !data->usb_charger_online)
+ offset = offset_discharger;
+ else if (data->usb_charger_online)
+ offset = offset_usb_charger;
+ else if (data->charger_online)
+ offset = offset_charger;
+
+ /* simple average */
+ for (i = 0; i < ADC_SAMPLE_COUNT; i++) {
+ adc_val = max11801_read_adc();
+ /* Check if touch driver is probed */
+ if (max11801_read_adc() < 0)
+ break;
+ voltage_data += adc_val - offset;
+ }
+ voltage_data = voltage_data / ADC_SAMPLE_COUNT;
+ dev_dbg(data->dev, "volt: %d\n", voltage_data);
+
+ return voltage_data;
+}
+
+static void max8903_battery_update_status(struct max8903_data *data)
+{
+ if (!data->pdata->feature_flag) {
+ data->voltage_uV = calibration_voltage(data);
+ data->percent = calibrate_battery_capability_percent(data);
+ if (data->percent != data->old_percent) {
+ data->old_percent = data->percent;
+ power_supply_changed(data->bat);
+ }
+ /*
+ * because boot time gap between led framwork and charger
+ * framwork,when system boots with charger attatched,
+ * charger led framwork loses the first charger online event,
+ * add once extra power_supply_changed can fix this issure
+ */
+ if (data->first_delay_count < 200) {
+ data->first_delay_count = data->first_delay_count + 1;
+ power_supply_changed(data->bat);
+ }
+ }
+}
+
+static int max8903_battery_get_property(struct power_supply *bat,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *di = power_supply_get_drvdata(bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (gpio_get_value(di->pdata->chg) == 0) {
+ di->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if ((di->ta_in || di->usb_in) &&
+ gpio_get_value(di->pdata->chg) > 0) {
+ if (!di->pdata->feature_flag) {
+ if (di->percent >= 99)
+ di->battery_status =
+ POWER_SUPPLY_STATUS_FULL;
+ else
+ di->battery_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ di->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+ val->intval = di->battery_status;
+ return 0;
+ default:
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = di->voltage_uV;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = HIGH_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = LOW_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->percent < 0 ? 0 :
+ (di->percent > 100 ? 100 : di->percent);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (di->fault)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (di->battery_status == POWER_SUPPLY_STATUS_FULL)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->percent <= 15)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max8903_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->ta_in)
+ val->intval = 1;
+ data->charger_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max8903_get_usb_property(struct power_supply *usb,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = power_supply_get_drvdata(usb);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in)
+ val->intval = 1;
+ data->usb_charger_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool ta_in = false;
+
+ if (pdata->dok)
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+ if (ta_in == data->ta_in)
+ return IRQ_HANDLED;
+
+ data->ta_in = ta_in;
+ dev_info(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(data->psy);
+ power_supply_changed(data->bat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool usb_in = false;
+
+ if (pdata->uok)
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+ if (usb_in == data->usb_in)
+ return IRQ_HANDLED;
+ data->usb_in = usb_in;
+ dev_info(data->dev, "USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(data->bat);
+ power_supply_changed(data->usb);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool fault;
+
+ fault = gpio_get_value(pdata->flt) ? false : true;
+
+ if (fault == data->fault)
+ return IRQ_HANDLED;
+ data->fault = fault;
+
+ if (fault)
+ dev_err(data->dev, "Charger suffers a fault and stops.\n");
+ else
+ dev_err(data->dev, "Charger recovered from a fault.\n");
+ max8903_charger_update_status(data);
+ power_supply_changed(data->psy);
+ power_supply_changed(data->bat);
+ power_supply_changed(data->usb);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_chg(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ int chg_state;
+
+ chg_state = gpio_get_value(pdata->chg) ? false : true;
+
+ if (chg_state == data->chg_state)
+ return IRQ_HANDLED;
+ data->chg_state = chg_state;
+ max8903_charger_update_status(data);
+ power_supply_changed(data->psy);
+ power_supply_changed(data->bat);
+ power_supply_changed(data->usb);
+
+ return IRQ_HANDLED;
+}
+
+static void max8903_battery_work(struct work_struct *work)
+{
+ struct max8903_data *data;
+
+ data = container_of(work, struct max8903_data, work.work);
+ data->interval = HZ * BATTERY_UPDATE_INTERVAL;
+
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ dev_dbg(data->dev, "battery voltage: %4d mV\n", data->voltage_uV);
+ dev_dbg(data->dev, "charger online status: %d\n",
+ data->charger_online);
+ dev_dbg(data->dev, "battery status : %d\n" , data->battery_status);
+ dev_dbg(data->dev, "battery capacity percent: %3d\n", data->percent);
+ dev_dbg(data->dev, "data->usb_in: %x , data->ta_in: %x\n",
+ data->usb_in, data->ta_in);
+ /* reschedule for the next time */
+ schedule_delayed_work(&data->work, data->interval);
+}
+
+static ssize_t max8903_voltage_offset_discharger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_discharger:%04d\n",
+ offset_discharger);
+}
+
+static ssize_t max8903_voltage_offset_discharger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ unsigned long data;
+
+ ret = kstrtoul(buf, 10, &data);
+ offset_discharger = (int)data;
+ pr_info("read offset_discharger:%04d\n", offset_discharger);
+
+ return count;
+}
+
+static ssize_t max8903_voltage_offset_charger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_charger:%04d\n",
+ offset_charger);
+}
+
+static ssize_t max8903_voltage_offset_charger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ unsigned long data;
+
+ ret = kstrtoul(buf, 10, &data);
+ offset_charger = (int)data;
+ pr_info("read offset_charger:%04d\n", offset_charger);
+ return count;
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_usb_charger:%04d\n",
+ offset_usb_charger);
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ int ret;
+ unsigned long data;
+
+ ret = kstrtoul(buf, 10, &data);
+ offset_usb_charger = (int)data;
+ pr_info("read offset_charger:%04d\n", offset_usb_charger);
+
+ return count;
+}
+
+static struct device_attribute max8903_discharger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_discharger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_discharger_show,
+ .store = max8903_voltage_offset_discharger_store,
+};
+
+static struct device_attribute max8903_charger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_charger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_charger_show,
+ .store = max8903_voltage_offset_charger_store,
+};
+
+static struct device_attribute max8903_usb_charger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_usb_charger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_usb_charger_show,
+ .store = max8903_voltage_offset_usb_charger_store,
+};
+
+#if defined(CONFIG_OF)
+static const struct of_device_id max8903_dt_ids[] = {
+ { .compatible = "fsl,max8903-charger", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max8903_dt_ids);
+
+static struct max8903_pdata *max8903_of_populate_pdata(
+ struct device *dev)
+{
+ struct device_node *of_node = dev->of_node;
+ struct max8903_pdata *pdata = dev->platform_data;
+
+ if (!of_node || pdata)
+ return pdata;
+
+ pdata = devm_kzalloc(dev, sizeof(struct max8903_pdata),
+ GFP_KERNEL);
+ if (!pdata)
+ return pdata;
+
+ if (of_get_property(of_node, "fsl,dcm_always_high", NULL))
+ pdata->dcm_always_high = true;
+ if (of_get_property(of_node, "fsl,dc_valid", NULL))
+ pdata->dc_valid = true;
+ if (of_get_property(of_node, "fsl,usb_valid", NULL))
+ pdata->usb_valid = true;
+ if (of_get_property(of_node, "fsl,adc_disable", NULL))
+ pdata->feature_flag = true;
+
+ if (pdata->dc_valid) {
+ pdata->dok = of_get_named_gpio(of_node, "dok_input", 0);
+ if (!gpio_is_valid(pdata->dok)) {
+ dev_err(dev, "pin pdata->dok: invalid gpio %d\n", pdata->dok);
+ return NULL;
+ }
+ }
+ if (pdata->usb_valid) {
+ pdata->uok = of_get_named_gpio(of_node, "uok_input", 0);
+ if (!gpio_is_valid(pdata->uok)) {
+ dev_err(dev, "pin pdata->uok: invalid gpio %d\n", pdata->uok);
+ return NULL;
+ }
+ }
+ pdata->chg = of_get_named_gpio(of_node, "chg_input", 0);
+ if (!gpio_is_valid(pdata->chg)) {
+ dev_err(dev, "pin pdata->chg: invalid gpio %d\n", pdata->chg);
+ return NULL;
+ }
+ pdata->flt = of_get_named_gpio(of_node, "flt_input", 0);
+ if (!gpio_is_valid(pdata->flt)) {
+ dev_err(dev, "pin pdata->flt: invalid gpio %d\n", pdata->flt);
+ return NULL;
+ }
+
+ /* no need check offset without adc converter */
+ if (!pdata->feature_flag) {
+ if (of_property_read_u32(of_node, "offset-charger",
+ &offset_charger))
+ dev_err(dev, "Not setting offset-charger in dts!\n");
+
+ if (of_property_read_u32(of_node, "offset-discharger",
+ &offset_discharger))
+ dev_err(dev, "Not setting offset-discharger in dts!\n");
+
+ if (of_property_read_u32(of_node, "offset-usb-charger",
+ &offset_usb_charger))
+ dev_err(dev, "Not setting offset-usb-charger in dts!\n");
+ }
+
+ return pdata;
+}
+#endif
+
+static const struct power_supply_desc max8903_ac_desc = {
+ .name = "max8903-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = max8903_get_property,
+ .properties = max8903_charger_props,
+ .num_properties = ARRAY_SIZE(max8903_charger_props),
+};
+
+static const struct power_supply_desc max8903_usb_desc = {
+ .name = "max8903-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .get_property = max8903_get_usb_property,
+ .properties = max8903_charger_props,
+ .num_properties = ARRAY_SIZE(max8903_charger_props),
+};
+
+static const struct power_supply_desc max8903_bat_desc = {
+ .name = "max8903-charger",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max8903_battery_props,
+ .num_properties = ARRAY_SIZE(max8903_battery_props),
+ .get_property = max8903_battery_get_property,
+ .use_for_apm = 1,
+};
+
+static int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio = 0;
+ int ta_in = 0;
+ int usb_in = 0;
+ struct power_supply_config psy_cfg = {};
+
+ data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ pdata = max8903_of_populate_pdata(&pdev->dev);
+ if (!pdata)
+ return -EINVAL;
+ }
+
+ data->first_delay_count = 0;
+ data->pdata = pdata;
+ data->dev = dev;
+ data->usb_in = 0;
+ data->ta_in = 0;
+ platform_set_drvdata(pdev, data);
+
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ if (pdata->dc_valid) {
+ if (pdata->dok && pdata->dcm_always_high) {
+ gpio = pdata->dok;
+ ret = gpio_request_one(gpio, GPIOF_IN, "max8903-DOK");
+ if (ret) {
+ dev_err(dev, "request max8903-DOK error!!\n");
+ goto err;
+ }
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When DC is wired, DOK and DCM should be"
+ " wired as well or set dcm always high!\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ if (pdata->uok) {
+ gpio = pdata->uok;
+ ret = gpio_request_one(gpio, GPIOF_IN, "max8903-UOK");
+ if (ret) {
+ dev_err(dev, "request max8903-UOK error!!\n");
+ goto err;
+ }
+ usb_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When USB is wired, UOK should be wired"
+ " as well.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->chg) {
+ ret = gpio_request_one(pdata->chg, GPIOF_IN, "max8903-CHG");
+ if (ret) {
+ dev_err(dev, "request max8903-CHG error!!\n");
+ goto err;
+ }
+ }
+
+ if (pdata->flt) {
+ ret = gpio_request_one(pdata->flt, GPIOF_IN, "max8903-FLT");
+ if (ret) {
+ dev_err(dev, "request max8903-FLT error!!\n");
+ goto err;
+ }
+ }
+
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+
+ psy_cfg.of_node = dev->of_node;
+ psy_cfg.drv_data = data;
+
+ data->psy = power_supply_register(dev, &max8903_ac_desc, &psy_cfg);
+ if (IS_ERR(data->psy)) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err;
+ }
+
+ data->usb = power_supply_register(dev, &max8903_usb_desc, &psy_cfg);
+ if (IS_ERR(data->usb)) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err_psy;
+ }
+
+ data->bat = power_supply_register(dev, &max8903_bat_desc, &psy_cfg);
+ if (IS_ERR(data->bat)) {
+ dev_err(data->dev, "failed to register battery\n");
+ goto err_usb;
+ }
+
+ INIT_DELAYED_WORK(&data->work, max8903_battery_work);
+ schedule_delayed_work(&data->work, data->interval);
+
+ if (pdata->dc_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->dok), NULL,
+ max8903_dcin, IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 DC IN",
+ data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+ gpio_to_irq(pdata->dok), ret);
+ goto err_dc_irq;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->uok), NULL,
+ max8903_usbin, IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 USB IN",
+ data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+ gpio_to_irq(pdata->uok), ret);
+ goto err_usb_irq;
+ }
+ }
+
+ if (pdata->flt) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->flt), NULL,
+ max8903_fault, IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Fault",
+ data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_flt_irq;
+ }
+ }
+
+ if (pdata->chg) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->chg), NULL,
+ max8903_chg, IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Status",
+ data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Status (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_chg_irq;
+ }
+ }
+
+ ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+ ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+ ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+
+ device_set_wakeup_capable(&pdev->dev, true);
+
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+
+ return 0;
+err_chg_irq:
+ if (pdata->chg)
+ free_irq(gpio_to_irq(pdata->chg), data);
+err_flt_irq:
+ if (pdata->flt)
+ free_irq(gpio_to_irq(pdata->flt), data);
+err_usb_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+err_dc_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ cancel_delayed_work(&data->work);
+ power_supply_unregister(data->bat);
+err_usb:
+ power_supply_unregister(data->usb);
+err_psy:
+ power_supply_unregister(data->psy);
+err:
+ if (pdata->uok)
+ gpio_free(pdata->uok);
+ if (pdata->dok)
+ gpio_free(pdata->dok);
+ if (pdata->flt)
+ gpio_free(pdata->flt);
+ if (pdata->chg)
+ gpio_free(pdata->chg);
+ return ret;
+}
+
+static int max8903_remove(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+
+ cancel_delayed_work_sync(&data->work);
+ power_supply_unregister(data->psy);
+ power_supply_unregister(data->usb);
+ power_supply_unregister(data->bat);
+
+ if (pdata->flt) {
+ free_irq(gpio_to_irq(pdata->flt), data);
+ gpio_free(pdata->flt);
+ }
+ if (pdata->usb_valid && pdata->uok) {
+ free_irq(gpio_to_irq(pdata->uok), data);
+ gpio_free(pdata->uok);
+ }
+ if (pdata->dc_valid) {
+ if (pdata->dok) {
+ free_irq(gpio_to_irq(pdata->dok), data);
+ gpio_free(pdata->dok);
+ } else if (pdata->chg) {
+ free_irq(gpio_to_irq(pdata->chg), data);
+ gpio_free(pdata->chg);
+ }
+ }
+
+ device_remove_file(&pdev->dev, &max8903_discharger_dev_attr);
+ device_remove_file(&pdev->dev, &max8903_charger_dev_attr);
+ device_remove_file(&pdev->dev, &max8903_usb_charger_dev_attr);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(data);
+ }
+
+ return 0;
+}
+
+static int max8903_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ int irq;
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+ if (pdata) {
+ if (pdata->dc_valid && device_may_wakeup(&pdev->dev)) {
+ irq = gpio_to_irq(pdata->dok);
+ enable_irq_wake(irq);
+ }
+
+ if (pdata->usb_valid && device_may_wakeup(&pdev->dev)) {
+ irq = gpio_to_irq(pdata->uok);
+ enable_irq_wake(irq);
+ }
+ cancel_delayed_work(&data->work);
+ }
+ }
+ return 0;
+}
+
+static int max8903_resume(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ bool ta_in = false;
+ bool usb_in = false;
+ int irq;
+
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+
+ if (pdata) {
+ if (pdata->dok)
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+ if (pdata->uok)
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+
+ if (ta_in != data->ta_in) {
+ data->ta_in = ta_in;
+ dev_info(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(data->psy);
+ }
+
+ if (usb_in != data->usb_in) {
+ data->usb_in = usb_in;
+ dev_info(data->dev, "USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(data->usb);
+ }
+
+ if (pdata->dc_valid && device_may_wakeup(&pdev->dev)) {
+ irq = gpio_to_irq(pdata->dok);
+ disable_irq_wake(irq);
+ }
+ if (pdata->usb_valid && device_may_wakeup(&pdev->dev)) {
+ irq = gpio_to_irq(pdata->uok);
+ disable_irq_wake(irq);
+ }
+
+ schedule_delayed_work(&data->work,
+ BATTERY_UPDATE_INTERVAL);
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver max8903_driver = {
+ .probe = max8903_probe,
+ .remove = max8903_remove,
+ .suspend = max8903_suspend,
+ .resume = max8903_resume,
+ .driver = {
+ .name = "max8903-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = max8903_dt_ids,
+ },
+};
+module_platform_driver(max8903_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Sabresd Battery Driver");
+MODULE_ALIAS("sabresd_battery");
diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig
index eca29d23dbb8..9919dc6943a3 100644
--- a/drivers/soc/fsl/Kconfig
+++ b/drivers/soc/fsl/Kconfig
@@ -31,6 +31,13 @@ config FSL_MC_DPIO
objects individually, but groups them under a service layer
API.
+config FSL_SLEEP_FSM
+ bool
+ help
+ This driver configures a hardware FSM (Finite State Machine) for deep sleep.
+ The FSM is used to finish clean-ups at the last stage of system entering deep
+ sleep, and also wakes up system when a wake up event happens.
+
config DPAA2_CONSOLE
tristate "QorIQ DPAA2 console driver"
depends on OF && (ARCH_LAYERSCAPE || COMPILE_TEST)
@@ -51,4 +58,13 @@ config FSL_QIXIS
Say y here to enable QIXIS system controller api. The qixis driver
provides FPGA functions to control system.
+config FSL_RCPM
+ bool "Freescale RCPM support"
+ depends on PM_SLEEP && (ARM || ARM64)
+ help
+ The NXP QorIQ Processors based on ARM Core have RCPM module
+ (Run Control and Power Management), which performs all device-level
+ tasks associated with power management, such as wakeup source control.
+ Note that currently this driver will not support PowerPC based
+ QorIQ processor.
endmenu
diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile
index 6e70a4b56cae..c904bcb72512 100644
--- a/drivers/soc/fsl/Makefile
+++ b/drivers/soc/fsl/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_FSL_QIXIS) += qixis_ctrl.o
obj-$(CONFIG_FSL_GUTS) += guts.o
obj-$(CONFIG_FSL_MC_DPIO) += dpio/
obj-$(CONFIG_DPAA2_CONSOLE) += dpaa2-console.o
+obj-$(CONFIG_FSL_RCPM) += rcpm.o
+obj-$(CONFIG_FSL_SLEEP_FSM) += sleep_fsm.o
diff --git a/drivers/soc/fsl/rcpm.c b/drivers/soc/fsl/rcpm.c
new file mode 100644
index 000000000000..a093dbe6d2cb
--- /dev/null
+++ b/drivers/soc/fsl/rcpm.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rcpm.c - Freescale QorIQ RCPM driver
+//
+// Copyright 2019 NXP
+//
+// Author: Ran Wang <ran.wang_1@nxp.com>
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/kernel.h>
+
+#define RCPM_WAKEUP_CELL_MAX_SIZE 7
+
+struct rcpm {
+ unsigned int wakeup_cells;
+ void __iomem *ippdexpcr_base;
+ bool little_endian;
+};
+
+/**
+ * rcpm_pm_prepare - performs device-level tasks associated with power
+ * management, such as programming related to the wakeup source control.
+ * @dev: Device to handle.
+ *
+ */
+static int rcpm_pm_prepare(struct device *dev)
+{
+ int i, ret, idx;
+ void __iomem *base;
+ struct wakeup_source *ws;
+ struct rcpm *rcpm;
+ struct device_node *np = dev->of_node;
+ u32 value[RCPM_WAKEUP_CELL_MAX_SIZE + 1];
+ u32 setting[RCPM_WAKEUP_CELL_MAX_SIZE] = {0};
+
+ rcpm = dev_get_drvdata(dev);
+ if (!rcpm)
+ return -EINVAL;
+
+ base = rcpm->ippdexpcr_base;
+ idx = wakeup_sources_read_lock();
+
+ /* Begin with first registered wakeup source */
+ for_each_wakeup_source(ws) {
+
+ /* skip object which is not attached to device */
+ if (!ws->dev || !ws->dev->parent)
+ continue;
+
+ ret = device_property_read_u32_array(ws->dev->parent,
+ "fsl,rcpm-wakeup", value,
+ rcpm->wakeup_cells + 1);
+
+ /* Wakeup source should refer to current rcpm device */
+ if (ret || (np->phandle != value[0]))
+ continue;
+
+ /* Property "#fsl,rcpm-wakeup-cells" of rcpm node defines the
+ * number of IPPDEXPCR register cells, and "fsl,rcpm-wakeup"
+ * of wakeup source IP contains an integer array: <phandle to
+ * RCPM node, IPPDEXPCR0 setting, IPPDEXPCR1 setting,
+ * IPPDEXPCR2 setting, etc>.
+ *
+ * So we will go thought them to collect setting data.
+ */
+ for (i = 0; i < rcpm->wakeup_cells; i++)
+ setting[i] |= value[i + 1];
+ }
+
+ wakeup_sources_read_unlock(idx);
+
+ /* Program all IPPDEXPCRn once */
+ for (i = 0; i < rcpm->wakeup_cells; i++) {
+ u32 tmp = setting[i];
+ void __iomem *address = base + i * 4;
+
+ if (!tmp)
+ continue;
+
+ /* We can only OR related bits */
+ if (rcpm->little_endian) {
+ tmp |= ioread32(address);
+ iowrite32(tmp, address);
+ } else {
+ tmp |= ioread32be(address);
+ iowrite32be(tmp, address);
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops rcpm_pm_ops = {
+ .prepare = rcpm_pm_prepare,
+};
+
+static int rcpm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ struct rcpm *rcpm;
+ int ret;
+
+ rcpm = devm_kzalloc(dev, sizeof(*rcpm), GFP_KERNEL);
+ if (!rcpm)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r)
+ return -ENODEV;
+
+ rcpm->ippdexpcr_base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(rcpm->ippdexpcr_base)) {
+ ret = PTR_ERR(rcpm->ippdexpcr_base);
+ return ret;
+ }
+
+ rcpm->little_endian = device_property_read_bool(
+ &pdev->dev, "little-endian");
+
+ ret = device_property_read_u32(&pdev->dev,
+ "#fsl,rcpm-wakeup-cells", &rcpm->wakeup_cells);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&pdev->dev, rcpm);
+
+ return 0;
+}
+
+static const struct of_device_id rcpm_of_match[] = {
+ { .compatible = "fsl,qoriq-rcpm-2.1+", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rcpm_of_match);
+
+static struct platform_driver rcpm_driver = {
+ .driver = {
+ .name = "rcpm",
+ .of_match_table = rcpm_of_match,
+ .pm = &rcpm_pm_ops,
+ },
+ .probe = rcpm_probe,
+};
+
+module_platform_driver(rcpm_driver);
diff --git a/drivers/soc/fsl/sleep_fsm.c b/drivers/soc/fsl/sleep_fsm.c
new file mode 100644
index 000000000000..a30309863822
--- /dev/null
+++ b/drivers/soc/fsl/sleep_fsm.c
@@ -0,0 +1,279 @@
+/*
+ * deep sleep FSM (finite-state machine) configuration
+ *
+ * Copyright 2018 NXP
+ *
+ * Author: Hongbo Zhang <hongbo.zhang@freescale.com>
+ * Chenhui Zhao <chenhui.zhao@freescale.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the above-listed copyright holders nor the
+ * names of any contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/types.h>
+
+#include "sleep_fsm.h"
+/*
+ * These values are from chip's reference manual. For example,
+ * the values for T1040 can be found in "8.4.3.8 Programming
+ * supporting deep sleep mode" of Chapter 8 "Run Control and
+ * Power Management (RCPM)".
+ * The default value can be applied to T104x, LS1021.
+ */
+struct fsm_reg_vals epu_default_val[] = {
+ /* EPGCR (Event Processor Global Control Register) */
+ {EPGCR, 0},
+ /* EPECR (Event Processor Event Control Registers) */
+ {EPECR0 + EPECR_STRIDE * 0, 0},
+ {EPECR0 + EPECR_STRIDE * 1, 0},
+ {EPECR0 + EPECR_STRIDE * 2, 0xF0004004},
+ {EPECR0 + EPECR_STRIDE * 3, 0x80000084},
+ {EPECR0 + EPECR_STRIDE * 4, 0x20000084},
+ {EPECR0 + EPECR_STRIDE * 5, 0x08000004},
+ {EPECR0 + EPECR_STRIDE * 6, 0x80000084},
+ {EPECR0 + EPECR_STRIDE * 7, 0x80000084},
+ {EPECR0 + EPECR_STRIDE * 8, 0x60000084},
+ {EPECR0 + EPECR_STRIDE * 9, 0x08000084},
+ {EPECR0 + EPECR_STRIDE * 10, 0x42000084},
+ {EPECR0 + EPECR_STRIDE * 11, 0x90000084},
+ {EPECR0 + EPECR_STRIDE * 12, 0x80000084},
+ {EPECR0 + EPECR_STRIDE * 13, 0x08000084},
+ {EPECR0 + EPECR_STRIDE * 14, 0x02000084},
+ {EPECR0 + EPECR_STRIDE * 15, 0x00000004},
+ /*
+ * EPEVTCR (Event Processor EVT Pin Control Registers)
+ * SCU8 triger EVT2, and SCU11 triger EVT9
+ */
+ {EPEVTCR0 + EPEVTCR_STRIDE * 0, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 1, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 2, 0x80000001},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 3, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 4, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 5, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 6, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 7, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 8, 0},
+ {EPEVTCR0 + EPEVTCR_STRIDE * 9, 0xB0000001},
+ /* EPCMPR (Event Processor Counter Compare Registers) */
+ {EPCMPR0 + EPCMPR_STRIDE * 0, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 1, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 2, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 3, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 4, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 5, 0x00000020},
+ {EPCMPR0 + EPCMPR_STRIDE * 6, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 7, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 8, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 9, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 10, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 11, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 12, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 13, 0},
+ {EPCMPR0 + EPCMPR_STRIDE * 14, 0x000000FF},
+ {EPCMPR0 + EPCMPR_STRIDE * 15, 0x000000FF},
+ /* EPCCR (Event Processor Counter Control Registers) */
+ {EPCCR0 + EPCCR_STRIDE * 0, 0},
+ {EPCCR0 + EPCCR_STRIDE * 1, 0},
+ {EPCCR0 + EPCCR_STRIDE * 2, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 3, 0},
+ {EPCCR0 + EPCCR_STRIDE * 4, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 5, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 6, 0},
+ {EPCCR0 + EPCCR_STRIDE * 7, 0},
+ {EPCCR0 + EPCCR_STRIDE * 8, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 9, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 10, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 11, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 12, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 13, 0},
+ {EPCCR0 + EPCCR_STRIDE * 14, 0x92840000},
+ {EPCCR0 + EPCCR_STRIDE * 15, 0x92840000},
+ /* EPSMCR (Event Processor SCU Mux Control Registers) */
+ {EPSMCR0 + EPSMCR_STRIDE * 0, 0},
+ {EPSMCR0 + EPSMCR_STRIDE * 1, 0},
+ {EPSMCR0 + EPSMCR_STRIDE * 2, 0x6C700000},
+ {EPSMCR0 + EPSMCR_STRIDE * 3, 0x2F000000},
+ {EPSMCR0 + EPSMCR_STRIDE * 4, 0x002F0000},
+ {EPSMCR0 + EPSMCR_STRIDE * 5, 0x00002E00},
+ {EPSMCR0 + EPSMCR_STRIDE * 6, 0x7C000000},
+ {EPSMCR0 + EPSMCR_STRIDE * 7, 0x30000000},
+ {EPSMCR0 + EPSMCR_STRIDE * 8, 0x64300000},
+ {EPSMCR0 + EPSMCR_STRIDE * 9, 0x00003000},
+ {EPSMCR0 + EPSMCR_STRIDE * 10, 0x65000030},
+ {EPSMCR0 + EPSMCR_STRIDE * 11, 0x31740000},
+ {EPSMCR0 + EPSMCR_STRIDE * 12, 0x7F000000},
+ {EPSMCR0 + EPSMCR_STRIDE * 13, 0x00003100},
+ {EPSMCR0 + EPSMCR_STRIDE * 14, 0x00000031},
+ {EPSMCR0 + EPSMCR_STRIDE * 15, 0x76000000},
+ /* EPACR (Event Processor Action Control Registers) */
+ {EPACR0 + EPACR_STRIDE * 0, 0},
+ {EPACR0 + EPACR_STRIDE * 1, 0},
+ {EPACR0 + EPACR_STRIDE * 2, 0},
+ {EPACR0 + EPACR_STRIDE * 3, 0x00000080},
+ {EPACR0 + EPACR_STRIDE * 4, 0},
+ {EPACR0 + EPACR_STRIDE * 5, 0x00000040},
+ {EPACR0 + EPACR_STRIDE * 6, 0},
+ {EPACR0 + EPACR_STRIDE * 7, 0},
+ {EPACR0 + EPACR_STRIDE * 8, 0},
+ {EPACR0 + EPACR_STRIDE * 9, 0x0000001C},
+ {EPACR0 + EPACR_STRIDE * 10, 0x00000020},
+ {EPACR0 + EPACR_STRIDE * 11, 0},
+ {EPACR0 + EPACR_STRIDE * 12, 0x00000003},
+ {EPACR0 + EPACR_STRIDE * 13, 0x06000000},
+ {EPACR0 + EPACR_STRIDE * 14, 0x04000000},
+ {EPACR0 + EPACR_STRIDE * 15, 0x02000000},
+ /* EPIMCR (Event Processor Input Mux Control Registers) */
+ {EPIMCR0 + EPIMCR_STRIDE * 0, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 1, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 2, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 3, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 4, 0x44000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 5, 0x40000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 6, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 7, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 8, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 9, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 10, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 11, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 12, 0x44000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 13, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 14, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 15, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 16, 0x6A000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 17, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 18, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 19, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 20, 0x48000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 21, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 22, 0x6C000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 23, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 24, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 25, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 26, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 27, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 28, 0x76000000},
+ {EPIMCR0 + EPIMCR_STRIDE * 29, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 30, 0},
+ {EPIMCR0 + EPIMCR_STRIDE * 31, 0x76000000},
+ /* EPXTRIGCR (Event Processor Crosstrigger Control Register) */
+ {EPXTRIGCR, 0x0000FFDF},
+ /* end */
+ {FSM_END_FLAG, 0},
+};
+
+struct fsm_reg_vals npc_default_val[] = {
+ /* NPC triggered Memory-Mapped Access Registers */
+ {NCR, 0x80000000},
+ {MCCR1, 0},
+ {MCSR1, 0},
+ {MMAR1LO, 0},
+ {MMAR1HI, 0},
+ {MMDR1, 0},
+ {MCSR2, 0},
+ {MMAR2LO, 0},
+ {MMAR2HI, 0},
+ {MMDR2, 0},
+ {MCSR3, 0x80000000},
+ {MMAR3LO, 0x000E2130},
+ {MMAR3HI, 0x00030000},
+ {MMDR3, 0x00020000},
+ /* end */
+ {FSM_END_FLAG, 0},
+};
+
+/**
+ * fsl_fsm_setup - Configure EPU's FSM registers
+ * @base: the base address of registers
+ * @val: Pointer to address-value pairs for FSM registers
+ */
+void fsl_fsm_setup(void __iomem *base, struct fsm_reg_vals *val)
+{
+ struct fsm_reg_vals *data = val;
+
+ WARN_ON(!base || !data);
+ while (data->offset != FSM_END_FLAG) {
+ iowrite32be(data->value, base + data->offset);
+ data++;
+ }
+}
+
+void fsl_epu_setup_default(void __iomem *epu_base)
+{
+ fsl_fsm_setup(epu_base, epu_default_val);
+}
+
+void fsl_npc_setup_default(void __iomem *npc_base)
+{
+ fsl_fsm_setup(npc_base, npc_default_val);
+}
+
+void fsl_epu_clean_default(void __iomem *epu_base)
+{
+ u32 offset;
+
+ /* follow the exact sequence to clear the registers */
+ /* Clear EPACRn */
+ for (offset = EPACR0; offset <= EPACR15; offset += EPACR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPEVTCRn */
+ for (offset = EPEVTCR0; offset <= EPEVTCR9; offset += EPEVTCR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPGCR */
+ iowrite32be(0, epu_base + EPGCR);
+
+ /* Clear EPSMCRn */
+ for (offset = EPSMCR0; offset <= EPSMCR15; offset += EPSMCR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPCCRn */
+ for (offset = EPCCR0; offset <= EPCCR31; offset += EPCCR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPCMPRn */
+ for (offset = EPCMPR0; offset <= EPCMPR31; offset += EPCMPR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPCTRn */
+ for (offset = EPCTR0; offset <= EPCTR31; offset += EPCTR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPIMCRn */
+ for (offset = EPIMCR0; offset <= EPIMCR31; offset += EPIMCR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+
+ /* Clear EPXTRIGCRn */
+ iowrite32be(0, epu_base + EPXTRIGCR);
+
+ /* Clear EPECRn */
+ for (offset = EPECR0; offset <= EPECR15; offset += EPECR_STRIDE)
+ iowrite32be(0, epu_base + offset);
+}
diff --git a/drivers/soc/fsl/sleep_fsm.h b/drivers/soc/fsl/sleep_fsm.h
new file mode 100644
index 000000000000..e0013c0d9984
--- /dev/null
+++ b/drivers/soc/fsl/sleep_fsm.h
@@ -0,0 +1,130 @@
+/*
+ * deep sleep FSM (finite-state machine) configuration
+ *
+ * Copyright 2018 NXP
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the above-listed copyright holders nor the
+ * names of any contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FSL_SLEEP_FSM_H
+#define _FSL_SLEEP_FSM_H
+
+#define FSL_STRIDE_4B 4
+#define FSL_STRIDE_8B 8
+
+/* End flag */
+#define FSM_END_FLAG 0xFFFFFFFFUL
+
+/* Block offsets */
+#define RCPM_BLOCK_OFFSET 0x00022000
+#define EPU_BLOCK_OFFSET 0x00000000
+#define NPC_BLOCK_OFFSET 0x00001000
+
+/* EPGCR (Event Processor Global Control Register) */
+#define EPGCR 0x000
+
+/* EPEVTCR0-9 (Event Processor EVT Pin Control Registers) */
+#define EPEVTCR0 0x050
+#define EPEVTCR9 0x074
+#define EPEVTCR_STRIDE FSL_STRIDE_4B
+
+/* EPXTRIGCR (Event Processor Crosstrigger Control Register) */
+#define EPXTRIGCR 0x090
+
+/* EPIMCR0-31 (Event Processor Input Mux Control Registers) */
+#define EPIMCR0 0x100
+#define EPIMCR31 0x17C
+#define EPIMCR_STRIDE FSL_STRIDE_4B
+
+/* EPSMCR0-15 (Event Processor SCU Mux Control Registers) */
+#define EPSMCR0 0x200
+#define EPSMCR15 0x278
+#define EPSMCR_STRIDE FSL_STRIDE_8B
+
+/* EPECR0-15 (Event Processor Event Control Registers) */
+#define EPECR0 0x300
+#define EPECR15 0x33C
+#define EPECR_STRIDE FSL_STRIDE_4B
+
+/* EPACR0-15 (Event Processor Action Control Registers) */
+#define EPACR0 0x400
+#define EPACR15 0x43C
+#define EPACR_STRIDE FSL_STRIDE_4B
+
+/* EPCCRi0-15 (Event Processor Counter Control Registers) */
+#define EPCCR0 0x800
+#define EPCCR15 0x83C
+#define EPCCR31 0x87C
+#define EPCCR_STRIDE FSL_STRIDE_4B
+
+/* EPCMPR0-15 (Event Processor Counter Compare Registers) */
+#define EPCMPR0 0x900
+#define EPCMPR15 0x93C
+#define EPCMPR31 0x97C
+#define EPCMPR_STRIDE FSL_STRIDE_4B
+
+/* EPCTR0-31 (Event Processor Counter Register) */
+#define EPCTR0 0xA00
+#define EPCTR31 0xA7C
+#define EPCTR_STRIDE FSL_STRIDE_4B
+
+/* NPC triggered Memory-Mapped Access Registers */
+#define NCR 0x000
+#define MCCR1 0x0CC
+#define MCSR1 0x0D0
+#define MMAR1LO 0x0D4
+#define MMAR1HI 0x0D8
+#define MMDR1 0x0DC
+#define MCSR2 0x0E0
+#define MMAR2LO 0x0E4
+#define MMAR2HI 0x0E8
+#define MMDR2 0x0EC
+#define MCSR3 0x0F0
+#define MMAR3LO 0x0F4
+#define MMAR3HI 0x0F8
+#define MMDR3 0x0FC
+
+/* RCPM Core State Action Control Register 0 */
+#define CSTTACR0 0xB00
+
+/* RCPM Core Group 1 Configuration Register 0 */
+#define CG1CR0 0x31C
+
+struct fsm_reg_vals {
+ u32 offset;
+ u32 value;
+};
+
+void fsl_fsm_setup(void __iomem *base, struct fsm_reg_vals *val);
+void fsl_epu_setup_default(void __iomem *epu_base);
+void fsl_npc_setup_default(void __iomem *npc_base);
+void fsl_epu_clean_default(void __iomem *epu_base);
+
+#endif /* _FSL_SLEEP_FSM_H */
diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig
index 406f00f312ee..21c2a9484a88 100644
--- a/drivers/soc/imx/Kconfig
+++ b/drivers/soc/imx/Kconfig
@@ -17,6 +17,12 @@ config IMX_SCU_SOC
Controller Unit SoC info module, it will provide the SoC info
like SoC family, ID and revision etc.
+config IMX8M_PM_DOMAINS
+ bool "i.MX8M PM domains"
+ depends on ARCH_MXC || (COMPILE_TEST && OF)
+ depends on PM
+ select PM_GENERIC_DOMAINS
+
config IMX8M_BUSFREQ
bool "i.MX8M busfreq"
depends on ARCH_MXC && ARM64
diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile
index e860d547aa56..7d5a129b4d35 100644
--- a/drivers/soc/imx/Makefile
+++ b/drivers/soc/imx/Makefile
@@ -4,4 +4,5 @@ obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o
obj-$(CONFIG_IMX8M_BUSFREQ) += busfreq-imx8mq.o
obj-$(CONFIG_ARM64) += soc-imx8.o
obj-$(CONFIG_IMX_SCU_SOC) += soc-imx-scu.o
+obj-$(CONFIG_IMX8M_PM_DOMAINS) += imx8m_pm_domains.o
obj-y += mu/
diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c
index 98b9d9a902ae..82c7a3a239cf 100644
--- a/drivers/soc/imx/gpc.c
+++ b/drivers/soc/imx/gpc.c
@@ -39,6 +39,11 @@
#define PGC_DOMAIN_FLAG_NO_PD BIT(0)
+#define GPC_PGC_DOMAIN_ARM 0
+#define GPC_PGC_DOMAIN_PU 1
+#define GPC_PGC_DOMAIN_DISPLAY 2
+#define GPC_PGC_DOMAIN_PCI 3
+
struct imx_pm_domain {
struct generic_pm_domain base;
struct regmap *regmap;
@@ -175,6 +180,8 @@ static int imx_pgc_parse_dt(struct device *dev, struct imx_pm_domain *domain)
return imx_pgc_get_clocks(dev, domain);
}
+static void imx_gpc_handle_ldobypass(struct platform_device *pdev);
+
static int imx_pgc_power_domain_probe(struct platform_device *pdev)
{
struct imx_pm_domain *domain = pdev->dev.platform_data;
@@ -201,6 +208,10 @@ static int imx_pgc_power_domain_probe(struct platform_device *pdev)
device_link_add(dev, dev->parent, DL_FLAG_AUTOREMOVE_CONSUMER);
+ /* Mark PU regulator as bypass */
+ if (pdev->id == GPC_PGC_DOMAIN_PU)
+ imx_gpc_handle_ldobypass(pdev);
+
return 0;
genpd_err:
@@ -238,11 +249,6 @@ static struct platform_driver imx_pgc_power_domain_driver = {
};
builtin_platform_driver(imx_pgc_power_domain_driver)
-#define GPC_PGC_DOMAIN_ARM 0
-#define GPC_PGC_DOMAIN_PU 1
-#define GPC_PGC_DOMAIN_DISPLAY 2
-#define GPC_PGC_DOMAIN_PCI 3
-
static struct genpd_power_state imx6_pm_domain_pu_state = {
.power_off_latency_ns = 25000,
.power_on_latency_ns = 2000000,
@@ -399,6 +405,22 @@ clk_err:
return ret;
}
+static void imx_gpc_handle_ldobypass(struct platform_device *pdev)
+{
+ struct imx_pm_domain *domain = pdev->dev.platform_data;
+ struct regulator *pu_reg = domain->supply;
+ u32 bypass = 0;
+ int ret;
+
+ ret = of_property_read_u32(pdev->dev.parent->of_node, "fsl,ldo-bypass", &bypass);
+ if (ret && ret != -EINVAL)
+ dev_warn(pdev->dev.parent, "failed to read fsl,ldo-bypass property: %d\n", ret);
+
+ /* We only bypass pu since arm and soc has been set in u-boot */
+ if (pu_reg && bypass)
+ regulator_allow_bypass(pu_reg, true);
+}
+
static int imx_gpc_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
@@ -453,6 +475,8 @@ static int imx_gpc_probe(struct platform_device *pdev)
of_id_data->num_domains);
if (ret)
return ret;
+
+ imx_gpc_handle_ldobypass(pdev);
} else {
struct imx_pm_domain *domain;
struct platform_device *pd_pdev;
diff --git a/drivers/soc/imx/imx8m_pm_domains.c b/drivers/soc/imx/imx8m_pm_domains.c
new file mode 100644
index 000000000000..ce06a059eaaa
--- /dev/null
+++ b/drivers/soc/imx/imx8m_pm_domains.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/regulator/consumer.h>
+
+#include <soc/imx/imx_sip.h>
+
+#define MAX_CLK_NUM 6
+#define to_imx8m_pm_domain(_genpd) container_of(_genpd, struct imx8m_pm_domain, pd)
+
+
+struct imx8m_pm_domain {
+ struct device *dev;
+ struct generic_pm_domain pd;
+ u32 domain_index;
+ struct clk *clk[MAX_CLK_NUM];
+ unsigned int num_clks;
+ struct regulator *reg;
+};
+
+enum imx8m_pm_domain_state {
+ PD_STATE_OFF,
+ PD_STATE_ON,
+};
+
+static DEFINE_MUTEX(gpc_pd_mutex);
+
+static int imx8m_pd_power_on(struct generic_pm_domain *genpd)
+{
+ struct imx8m_pm_domain *domain = to_imx8m_pm_domain(genpd);
+ struct arm_smccc_res res;
+ int index, ret = 0;
+
+ /* power on the external supply */
+ if (!IS_ERR(domain->reg)) {
+ ret = regulator_enable(domain->reg);
+ if (ret) {
+ dev_warn(domain->dev, "failed to power up the reg%d\n", ret);
+ return ret;
+ }
+ }
+
+ /* enable the necessary clks needed by the power domain */
+ if (domain->num_clks) {
+ for (index = 0; index < domain->num_clks; index++)
+ clk_prepare_enable(domain->clk[index]);
+ }
+
+ mutex_lock(&gpc_pd_mutex);
+ arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_CONFIG_GPC_PM_DOMAIN, domain->domain_index,
+ PD_STATE_ON, 0, 0, 0, 0, &res);
+ mutex_unlock(&gpc_pd_mutex);
+
+ return 0;
+}
+
+static int imx8m_pd_power_off(struct generic_pm_domain *genpd)
+{
+ struct imx8m_pm_domain *domain = to_imx8m_pm_domain(genpd);
+ struct arm_smccc_res res;
+ int index, ret = 0;
+
+ mutex_lock(&gpc_pd_mutex);
+ arm_smccc_smc(IMX_SIP_GPC, IMX_SIP_CONFIG_GPC_PM_DOMAIN, domain->domain_index,
+ PD_STATE_OFF, 0, 0, 0, 0, &res);
+ mutex_unlock(&gpc_pd_mutex);
+
+ /* power off the external supply */
+ if (!IS_ERR(domain->reg)) {
+ ret = regulator_disable(domain->reg);
+ if (ret) {
+ dev_warn(domain->dev, "failed to power off the reg%d\n", ret);
+ return ret;
+ }
+ }
+
+ /* disable clks when power domain is off */
+ if (domain->num_clks) {
+ for (index = 0; index < domain->num_clks; index++)
+ clk_disable_unprepare(domain->clk[index]);
+ }
+
+ return ret;
+};
+
+static int imx8m_pd_get_clocks(struct imx8m_pm_domain *domain)
+{
+ int i, ret;
+
+ for (i = 0; ; i++) {
+ struct clk *clk = of_clk_get(domain->dev->of_node, i);
+ if (IS_ERR(clk))
+ break;
+ if (i >= MAX_CLK_NUM) {
+ dev_err(domain->dev, "more than %d clocks\n",
+ MAX_CLK_NUM);
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ domain->clk[i] = clk;
+ }
+ domain->num_clks = i;
+
+ return 0;
+
+clk_err:
+ while (i--)
+ clk_put(domain->clk[i]);
+
+ return ret;
+}
+
+static void imx8m_pd_put_clocks(struct imx8m_pm_domain *domain)
+{
+ int i;
+
+ for (i = domain->num_clks - 1; i >= 0; i--)
+ clk_put(domain->clk[i]);
+}
+
+static const struct of_device_id imx8m_pm_domain_ids[] = {
+ {.compatible = "fsl,imx8m-pm-domain"},
+ {},
+};
+
+static int imx8m_pm_domain_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct imx8m_pm_domain *domain;
+ struct of_phandle_args parent, child;
+ int ret;
+
+ domain = devm_kzalloc(dev, sizeof(*domain), GFP_KERNEL);
+ if (!domain)
+ return -ENOMEM;
+
+ child.np = np;
+ domain->dev = dev;
+
+ ret = of_property_read_string(np, "domain-name", &domain->pd.name);
+ if (ret) {
+ dev_err(dev, "failed to get the domain name\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(np, "domain-index", &domain->domain_index);
+ if (ret) {
+ dev_err(dev, "failed to get the domain index\n");
+ return -EINVAL;
+ }
+
+ domain->reg = devm_regulator_get_optional(dev, "power");
+ if (IS_ERR(domain->reg)) {
+ if (PTR_ERR(domain->reg) != -ENODEV) {
+ if (PTR_ERR(domain->reg) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get domain's regulator\n");
+ return PTR_ERR(domain->reg);
+ }
+ }
+
+ ret = imx8m_pd_get_clocks(domain);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get domain's clocks\n");
+ return ret;
+ }
+
+ domain->pd.power_off = imx8m_pd_power_off;
+ domain->pd.power_on = imx8m_pd_power_on;
+
+ pm_genpd_init(&domain->pd, NULL, true);
+
+ ret = of_genpd_add_provider_simple(np, &domain->pd);
+ if (ret) {
+ dev_err(dev, "failed to add the domain provider\n");
+ pm_genpd_remove(&domain->pd);
+ imx8m_pd_put_clocks(domain);
+ return ret;
+ }
+
+ /* add it as subdomain if necessary */
+ if (!of_parse_phandle_with_args(np, "parent-domains",
+ "#power-domain-cells", 0, &parent)) {
+ ret = of_genpd_add_subdomain(&parent, &child);
+ of_node_put(parent.np);
+
+ if (ret < 0) {
+ dev_dbg(dev, "failed to add the subdomain: %s: %d",
+ domain->pd.name, ret);
+ of_genpd_del_provider(np);
+ pm_genpd_remove(&domain->pd);
+ imx8m_pd_put_clocks(domain);
+ return driver_deferred_probe_check_state(dev);
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver imx8m_pm_domain_driver = {
+ .driver = {
+ .name = "imx8m_pm_domain",
+ .owner = THIS_MODULE,
+ .of_match_table = imx8m_pm_domain_ids,
+ },
+ .probe = imx8m_pm_domain_probe,
+};
+module_platform_driver(imx8m_pm_domain_driver);
+
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("NXP i.MX8M power domain driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/imx_sema4.h b/include/linux/imx_sema4.h
new file mode 100644
index 000000000000..19850ae7742b
--- /dev/null
+++ b/include/linux/imx_sema4.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_IMX_SEMA4_H__
+#define __LINUX_IMX_SEMA4_H__
+
+#define SEMA4_NUM_DEVICES 1
+#define SEMA4_NUM_GATES 16
+
+#define SEMA4_UNLOCK 0x00
+#define SEMA4_A9_LOCK 0x01
+#define SEMA4_GATE_MASK 0x03
+
+#define CORE_MUTEX_VALID (('c'<<24)|('m'<<24)|('t'<<24)|'x')
+
+/*
+ * The enumerates
+ */
+enum {
+ /* sema4 registers offset */
+ SEMA4_CP0INE = 0x40,
+ SEMA4_CP1INE = 0x48,
+ SEMA4_CP0NTF = 0x80,
+ SEMA4_CP1NTF = 0x88,
+};
+
+static const unsigned int idx_sema4[SEMA4_NUM_GATES] = {
+ 1 << 7, 1 << 6, 1 << 5, 1 << 4,
+ 1 << 3, 1 << 2, 1 << 1, 1 << 0,
+ 1 << 15, 1 << 14, 1 << 13, 1 << 12,
+ 1 << 11, 1 << 10, 1 << 9, 1 << 8,
+};
+
+struct imx_sema4_mutex {
+ u32 valid;
+ u32 gate_num;
+ unsigned char gate_val;
+ wait_queue_head_t wait_q;
+};
+
+struct imx_sema4_mutex_device {
+ struct device *dev;
+ u16 cpntf_val;
+ u16 cpine_val;
+ void __iomem *ioaddr; /* Mapped address */
+ spinlock_t lock; /* Mutex */
+ int irq;
+
+ u16 alloced;
+ struct imx_sema4_mutex *mutex_ptr[SEMA4_NUM_GATES];
+};
+
+struct imx_sema4_mutex *
+ imx_sema4_mutex_create(u32 dev_num, u32 mutex_num);
+#ifdef CONFIG_IMX_SEMA4
+int imx_sema4_mutex_destroy(struct imx_sema4_mutex *mutex_ptr);
+int imx_sema4_mutex_trylock(struct imx_sema4_mutex *mutex_ptr);
+int imx_sema4_mutex_lock(struct imx_sema4_mutex *mutex_ptr);
+int imx_sema4_mutex_unlock(struct imx_sema4_mutex *mutex_ptr);
+#else
+static inline int imx_sema4_mutex_destroy(struct imx_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int imx_sema4_mutex_trylock(struct imx_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int imx_sema4_mutex_lock(struct imx_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+static inline int imx_sema4_mutex_unlock(struct imx_sema4_mutex *mutex_ptr)
+{
+ return 0;
+}
+#endif
+#endif /* __LINUX_IMX_SEMA4_H__ */
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index baf02ff91a31..a7c726413984 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -143,6 +143,7 @@ struct generic_pm_domain {
};
};
+ unsigned int state_idx_saved; /* saved power state for recovery after system suspend/resume */
};
static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd)
diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h
index 661efa029c96..aa3da6611533 100644
--- a/include/linux/pm_wakeup.h
+++ b/include/linux/pm_wakeup.h
@@ -63,6 +63,11 @@ struct wakeup_source {
bool autosleep_enabled:1;
};
+#define for_each_wakeup_source(ws) \
+ for ((ws) = wakeup_sources_walk_start(); \
+ (ws); \
+ (ws) = wakeup_sources_walk_next((ws)))
+
#ifdef CONFIG_PM_SLEEP
/*
@@ -92,6 +97,10 @@ extern void wakeup_source_remove(struct wakeup_source *ws);
extern struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name);
extern void wakeup_source_unregister(struct wakeup_source *ws);
+extern int wakeup_sources_read_lock(void);
+extern void wakeup_sources_read_unlock(int idx);
+extern struct wakeup_source *wakeup_sources_walk_start(void);
+extern struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws);
extern int device_wakeup_enable(struct device *dev);
extern int device_wakeup_disable(struct device *dev);
extern void device_set_wakeup_capable(struct device *dev, bool capable);
diff --git a/include/linux/power/sabresd_battery.h b/include/linux/power/sabresd_battery.h
new file mode 100644
index 000000000000..10bfa4588864
--- /dev/null
+++ b/include/linux/power/sabresd_battery.h
@@ -0,0 +1,65 @@
+/*
+ * sabresd_battery.h - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2015 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef __MAX8903_SABRESD_H__
+#define __MAX8903_SABRESD_H__
+
+struct max8903_pdata {
+ /*
+ * GPIOs
+ * cen, chg, flt, and usus are optional.
+ * dok, dcm, and uok are not optional depending on the status of
+ * dc_valid and usb_valid.
+ */
+ int cen; /* Charger Enable input */
+ int dok; /* DC(Adapter) Power OK output */
+ int uok; /* USB Power OK output */
+ int chg; /* Charger status output */
+ int flt; /* Fault output */
+ int dcm; /* Current-Limit Mode input (1: DC, 2: USB) */
+ int usus; /* USB Suspend Input (1: suspended) */
+ int feature_flag;/* battery capacity feature(0:enable, 1:disable) */
+
+ /*
+ * DCM wired to Logic High Set this true when DCM pin connect to
+ * Logic high.
+ */
+ bool dcm_always_high;
+
+ /*
+ * DC(Adapter/TA) is wired
+ * When dc_valid is true,
+ * dok and dcm should be valid.
+ *
+ * At least one of dc_valid or usb_valid should be true.
+ */
+ bool dc_valid;
+ /*
+ * USB is wired
+ * When usb_valid is true,
+ * uok should be valid.
+ */
+ bool usb_valid;
+};
+
+#endif /* __SABRESD_BATTERY_H__ */
diff --git a/include/soc/imx/imx_sip.h b/include/soc/imx/imx_sip.h
new file mode 100644
index 000000000000..6b96b33c870e
--- /dev/null
+++ b/include/soc/imx/imx_sip.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019 NXP
+ */
+
+#ifndef __IMX_SIP_H__
+#define __IMX_SIP_H__
+
+#define IMX_SIP_GPC 0xC2000000
+#define IMX_SIP_CONFIG_GPC_PM_DOMAIN 0x03
+
+#endif /* __IMX_SIP_H__ */