diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,mpm.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,mpm.yaml index ebb40c48950ab..3f9645fbc3c20 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/qcom,mpm.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,mpm.yaml @@ -68,8 +68,8 @@ properties: - description: MPM pin number - description: GIC SPI number for the MPM pin - '#power-domain-cells': - const: 0 + power-domains: + maxItems: 1 required: - compatible @@ -113,6 +113,6 @@ examples: <24 79>, <86 183>, <91 260>; - #power-domain-cells = <0>; + power-domains = <&cluster_pd>; }; }; diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 812ad89a649e2..07055b2dec959 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -66,6 +66,8 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x0>; enable-method = "psci"; + power-domains = <&cpu_pd0>; + power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -84,6 +86,8 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x100>; enable-method = "psci"; + power-domains = <&cpu_pd1>; + power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -102,6 +106,8 @@ compatible = "arm,cortex-a55"; reg = <0x0 0x200>; enable-method = "psci"; + power-domains = <&cpu_pd2>; + power-domain-names = "psci"; next-level-cache = <&l3>; capacity-dmips-mhz = <1024>; dynamic-power-coefficient = <100>; @@ -120,6 +126,8 @@ compatible = "arm,cortex-a78c"; reg = <0x0 0x300>; enable-method = "psci"; + power-domains = <&cpu_pd3>; + power-domain-names = "psci"; next-level-cache = <&l2_3>; capacity-dmips-mhz = <1946>; dynamic-power-coefficient = <489>; @@ -163,6 +171,61 @@ }; }; + idle-states { + entry-method = "psci"; + + little_cpu_sleep_0: cpu-sleep-0-0 { + compatible = "arm,idle-state"; + idle-state-name = "little-power-down"; + arm,psci-suspend-param = <0x40000003>; + entry-latency-us = <549>; + exit-latency-us = <901>; + min-residency-us = <1774>; + local-timer-stop; + }; + + little_cpu_sleep_1: cpu-sleep-0-1 { + compatible = "arm,idle-state"; + idle-state-name = "little-pll-power-down"; + arm,psci-suspend-param = <0x40000004>; + entry-latency-us = <702>; + exit-latency-us = <915>; + min-residency-us = <4001>; + local-timer-stop; + }; + + big_cpu_sleep_0: cpu-sleep-1-0 { + compatible = "arm,idle-state"; + idle-state-name = "big-power-down"; + arm,psci-suspend-param = <0x40000003>; + entry-latency-us = <523>; + exit-latency-us = <1244>; + min-residency-us = <2207>; + local-timer-stop; + }; + + big_cpu_sleep_1: cpu-sleep-1-1 { + compatible = "arm,idle-state"; + idle-state-name = "big-pll-power-down"; + arm,psci-suspend-param = <0x40000004>; + entry-latency-us = <526>; + exit-latency-us = <1854>; + min-residency-us = <5555>; + local-timer-stop; + }; + }; + + domain_idle_states: domain-idle-states { + cluster_sleep_apss_off: cluster-sleep-0 { + compatible = "domain-idle-state"; + idle-state-name = "cluster-power-down"; + arm,psci-suspend-param = <0x41000044>; + entry-latency-us = <2752>; + exit-latency-us = <3038>; + min-residency-us = <6118>; + }; + }; + l3: l3-cache { compatible = "cache"; cache-level = <3>; @@ -281,6 +344,35 @@ psci: psci { compatible = "arm,psci-1.0"; method = "smc"; + + cpu_pd0: power-domain-cpu0 { + #power-domain-cells = <0>; + power-domains = <&cluster_pd>; + domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; + }; + + cpu_pd1: power-domain-cpu1 { + #power-domain-cells = <0>; + power-domains = <&cluster_pd>; + domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; + }; + + cpu_pd2: power-domain-cpu2 { + #power-domain-cells = <0>; + power-domains = <&cluster_pd>; + domain-idle-states = <&little_cpu_sleep_0 &little_cpu_sleep_1>; + }; + + cpu_pd3: power-domain-cpu3 { + #power-domain-cells = <0>; + power-domains = <&cluster_pd>; + domain-idle-states = <&big_cpu_sleep_0 &big_cpu_sleep_1>; + }; + + cluster_pd: power-domain-cluster { + #power-domain-cells = <0>; + domain-idle-states = <&cluster_sleep_apss_off>; + }; }; rpm: remoteproc { @@ -354,8 +446,8 @@ mboxes = <&apcs_glb 1>; interrupt-controller; #interrupt-cells = <2>; - #power-domain-cells = <0>; interrupt-parent = <&intc>; + power-domains = <&cluster_pd>; qcom,mpm-pin-count = <96>; qcom,mpm-pin-map = <2 275>, /* TSENS0 uplow */ <12 422>, /* DWC3 ss_phy_irq */ diff --git a/drivers/irqchip/irq-qcom-mpm.c b/drivers/irqchip/irq-qcom-mpm.c index 181320528a47a..f43c4a1c35f78 100644 --- a/drivers/irqchip/irq-qcom-mpm.c +++ b/drivers/irqchip/irq-qcom-mpm.c @@ -4,6 +4,8 @@ * Copyright (c) 2010-2020, The Linux Foundation. All rights reserved. */ +#include +#include #include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -18,10 +21,13 @@ #include #include #include +#include #include #include #include +#include + /* * This is the driver for Qualcomm MPM (MSM Power Manager) interrupt controller, * which is commonly found on Qualcomm SoCs built on the RPM architecture. @@ -63,11 +69,23 @@ * */ -#define MPM_REG_ENABLE 0 -#define MPM_REG_FALLING_EDGE 1 -#define MPM_REG_RISING_EDGE 2 -#define MPM_REG_POLARITY 3 -#define MPM_REG_STATUS 4 +#define MPM_TIMER_REGS 2 + +enum qcom_mpm_reg { + MPM_REG_TIMER = 0, + MPM_REG_ENABLE, + MPM_REG_FALLING_EDGE, + MPM_REG_RISING_EDGE, + MPM_REG_POLARITY, + MPM_REG_STATUS, +}; + +#define USECS_TO_CYCLES(time_usecs) xloops_to_cycles((time_usecs) * 0x10C7UL) + +static inline unsigned long xloops_to_cycles(u64 xloops) +{ + return (xloops * loops_per_jiffy * HZ) >> 32; +} /* MPM pin map to GIC hwirq */ struct mpm_gic_map { @@ -76,6 +94,7 @@ struct mpm_gic_map { }; struct qcom_mpm_priv { + struct device *dev; void __iomem *base; raw_spinlock_t lock; struct mbox_client mbox_client; @@ -84,21 +103,41 @@ struct qcom_mpm_priv { unsigned int map_cnt; unsigned int reg_stride; struct irq_domain *domain; - struct generic_pm_domain genpd; + struct notifier_block genpd_nb; + struct notifier_block mpm_pm; + atomic_t cpus_in_pm; }; -static u32 qcom_mpm_read(struct qcom_mpm_priv *priv, unsigned int reg, - unsigned int index) +static unsigned int qcom_mpm_offset(struct qcom_mpm_priv *priv, enum qcom_mpm_reg reg, + unsigned int index) { - unsigned int offset = (reg * priv->reg_stride + index + 2) * 4; + unsigned int reg_offset; + + /* + * Per the vMPM register map, TIMER[0..1] starts at register index 0 and all pin-specific + * registers start after the two TIMER regs. Pin-specific register IDs start at + * MPM_REG_ENABLE, so subtract it to convert to a zero-based pin-register group index. + */ + if (reg == MPM_REG_TIMER) + reg_offset = index; + else + reg_offset = MPM_TIMER_REGS + + (reg - MPM_REG_ENABLE) * priv->reg_stride + index; + + return reg_offset * sizeof(u32); +} + +static u32 qcom_mpm_read(struct qcom_mpm_priv *priv, enum qcom_mpm_reg reg, unsigned int index) +{ + unsigned int offset = qcom_mpm_offset(priv, reg, index); return readl_relaxed(priv->base + offset); } -static void qcom_mpm_write(struct qcom_mpm_priv *priv, unsigned int reg, +static void qcom_mpm_write(struct qcom_mpm_priv *priv, enum qcom_mpm_reg reg, unsigned int index, u32 val) { - unsigned int offset = (reg * priv->reg_stride + index + 2) * 4; + unsigned int offset = qcom_mpm_offset(priv, reg, index); writel_relaxed(val, priv->base + offset); @@ -139,7 +178,7 @@ static void qcom_mpm_unmask(struct irq_data *d) irq_chip_unmask_parent(d); } -static void mpm_set_type(struct qcom_mpm_priv *priv, bool set, unsigned int reg, +static void mpm_set_type(struct qcom_mpm_priv *priv, bool set, enum qcom_mpm_reg reg, unsigned int index, unsigned int shift) { unsigned long flags, val; @@ -292,10 +331,38 @@ static irqreturn_t qcom_mpm_handler(int irq, void *dev_id) return ret; } -static int mpm_pd_power_off(struct generic_pm_domain *genpd) +static void mpm_write_next_wakeup(struct qcom_mpm_priv *priv) +{ + ktime_t now, wakeup = KTIME_MAX; + u64 wakeup_us, wakeup_cycles = ~0; + u32 lo, hi; + + /* Set highest time when system (timekeeping) is suspended */ + if (system_state == SYSTEM_SUSPEND) + goto exit; + + /* Find the relative wakeup in kernel time scale */ + wakeup = dev_pm_genpd_get_next_hrtimer(priv->dev); + + /* Find the relative wakeup in kernel time scale */ + now = ktime_get(); + wakeup = ktime_sub(wakeup, now); + wakeup_us = ktime_to_us(wakeup); + + /* Convert the wakeup to arch timer scale */ + wakeup_cycles = USECS_TO_CYCLES(wakeup_us); + wakeup_cycles += arch_timer_read_counter(); + +exit: + lo = wakeup_cycles; + hi = wakeup_cycles >> 32; + + qcom_mpm_write(priv, MPM_REG_TIMER, 0, lo); + qcom_mpm_write(priv, MPM_REG_TIMER, 1, hi); +} + +static int handle_rpm_notification(struct qcom_mpm_priv *priv) { - struct qcom_mpm_priv *priv = container_of(genpd, struct qcom_mpm_priv, - genpd); int i, ret; for (i = 0; i < priv->reg_stride; i++) @@ -306,11 +373,60 @@ static int mpm_pd_power_off(struct generic_pm_domain *genpd) if (ret < 0) return ret; + mpm_write_next_wakeup(priv); mbox_client_txdone(priv->mbox_chan, 0); - return 0; } +static int mpm_pd_power_cb(struct notifier_block *nb, unsigned long action, void *d) +{ + struct qcom_mpm_priv *priv = container_of(nb, struct qcom_mpm_priv, + genpd_nb); + + switch (action) { + case GENPD_NOTIFY_PRE_OFF: + if (handle_rpm_notification(priv)) + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static int mpm_cpu_pm_callback(struct notifier_block *nfb, unsigned long action, void *v) +{ + struct qcom_mpm_priv *priv = container_of(nfb, struct qcom_mpm_priv, mpm_pm); + int cpus_in_pm; + + switch (action) { + case CPU_PM_ENTER: + cpus_in_pm = atomic_inc_return(&priv->cpus_in_pm); + /* + * NOTE: comments for num_online_cpus() point out that it's + * only a snapshot so we need to be careful. It should be OK + * for us to use, though. It's important for us not to miss + * if we're the last CPU going down so it would only be a + * problem if a CPU went offline right after we did the check + * AND that CPU was not idle AND that CPU was the last non-idle + * CPU. That can't happen. CPUs would have to come out of idle + * before the CPU could go offline. + */ + if (cpus_in_pm < num_online_cpus()) + return NOTIFY_OK; + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + atomic_dec(&priv->cpus_in_pm); + return NOTIFY_OK; + default: + return NOTIFY_DONE; + } + + if (handle_rpm_notification(priv)) + return NOTIFY_BAD; + + return NOTIFY_OK; +} + static bool gic_hwirq_is_mapped(struct mpm_gic_map *maps, int cnt, u32 hwirq) { int i; @@ -327,7 +443,6 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct irq_domain *parent_domain; - struct generic_pm_domain *genpd; struct device_node *msgram_np; struct qcom_mpm_priv *priv; unsigned int pin_cnt; @@ -339,6 +454,8 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare if (!priv) return -ENOMEM; + priv->dev = &pdev->dev; + ret = of_property_read_u32(np, "qcom,mpm-pin-count", &pin_cnt); if (ret) { dev_err(dev, "failed to read qcom,mpm-pin-count: %d\n", ret); @@ -415,26 +532,6 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare if (irq < 0) return irq; - genpd = &priv->genpd; - genpd->flags = GENPD_FLAG_IRQ_SAFE; - genpd->power_off = mpm_pd_power_off; - - genpd->name = devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev)); - if (!genpd->name) - return -ENOMEM; - - ret = pm_genpd_init(genpd, NULL, false); - if (ret) { - dev_err(dev, "failed to init genpd: %d\n", ret); - return ret; - } - - ret = of_genpd_add_provider_simple(np, genpd); - if (ret) { - dev_err(dev, "failed to add genpd provider: %d\n", ret); - goto remove_genpd; - } - priv->mbox_client.dev = dev; priv->mbox_client.knows_txdone = true; priv->mbox_chan = mbox_request_channel(&priv->mbox_client, 0); @@ -469,14 +566,24 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare goto remove_domain; } + if (of_find_property(np, "power-domains", NULL)) { + devm_pm_runtime_enable(dev); + priv->genpd_nb.notifier_call = mpm_pd_power_cb; + ret = dev_pm_genpd_add_notifier(dev, &priv->genpd_nb); + } else { + priv->mpm_pm.notifier_call = mpm_cpu_pm_callback; + ret = cpu_pm_register_notifier(&priv->mpm_pm); + } + + if (ret) + goto remove_domain; + return 0; remove_domain: irq_domain_remove(priv->domain); free_mbox: mbox_free_channel(priv->mbox_chan); -remove_genpd: - pm_genpd_remove(genpd); return ret; }