diff options
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__ */ |