net: ipa: rename "ipa_clock.c"
authorAlex Elder <elder@linaro.org>
Fri, 20 Aug 2021 16:01:29 +0000 (11:01 -0500)
committerDavid S. Miller <davem@davemloft.net>
Sun, 22 Aug 2021 08:44:17 +0000 (09:44 +0100)
Finally, rename "ipa_clock.c" to be "ipa_power.c" and "ipa_clock.h"
to be "ipa_power.h".

Signed-off-by: Alex Elder <elder@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ipa/Makefile
drivers/net/ipa/ipa_clock.c [deleted file]
drivers/net/ipa/ipa_clock.h [deleted file]
drivers/net/ipa/ipa_endpoint.c
drivers/net/ipa/ipa_main.c
drivers/net/ipa/ipa_modem.c
drivers/net/ipa/ipa_power.c [new file with mode: 0644]
drivers/net/ipa/ipa_power.h [new file with mode: 0644]

index 75435d4..bdfb243 100644 (file)
@@ -1,6 +1,6 @@
 obj-$(CONFIG_QCOM_IPA) +=      ipa.o
 
-ipa-y                  :=      ipa_main.o ipa_clock.o ipa_reg.o ipa_mem.o \
+ipa-y                  :=      ipa_main.o ipa_power.o ipa_reg.o ipa_mem.o \
                                ipa_table.o ipa_interrupt.o gsi.o gsi_trans.o \
                                ipa_gsi.o ipa_smp2p.o ipa_uc.o \
                                ipa_endpoint.o ipa_cmd.o ipa_modem.o \
diff --git a/drivers/net/ipa/ipa_clock.c b/drivers/net/ipa/ipa_clock.c
deleted file mode 100644 (file)
index 3ebc44e..0000000
+++ /dev/null
@@ -1,473 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
- * Copyright (C) 2018-2021 Linaro Ltd.
- */
-
-#include <linux/clk.h>
-#include <linux/device.h>
-#include <linux/interconnect.h>
-#include <linux/pm.h>
-#include <linux/pm_runtime.h>
-#include <linux/bitops.h>
-
-#include "ipa.h"
-#include "ipa_clock.h"
-#include "ipa_endpoint.h"
-#include "ipa_modem.h"
-#include "ipa_data.h"
-
-/**
- * DOC: IPA Power Management
- *
- * The IPA hardware is enabled when the IPA core clock and all the
- * interconnects (buses) it depends on are enabled.  Runtime power
- * management is used to determine whether the core clock and
- * interconnects are enabled, and if not in use to be suspended
- * automatically.
- *
- * The core clock currently runs at a fixed clock rate when enabled,
- * an all interconnects use a fixed average and peak bandwidth.
- */
-
-#define IPA_AUTOSUSPEND_DELAY  500     /* milliseconds */
-
-/**
- * struct ipa_interconnect - IPA interconnect information
- * @path:              Interconnect path
- * @average_bandwidth: Average interconnect bandwidth (KB/second)
- * @peak_bandwidth:    Peak interconnect bandwidth (KB/second)
- */
-struct ipa_interconnect {
-       struct icc_path *path;
-       u32 average_bandwidth;
-       u32 peak_bandwidth;
-};
-
-/**
- * enum ipa_power_flag - IPA power flags
- * @IPA_POWER_FLAG_RESUMED:    Whether resume from suspend has been signaled
- * @IPA_POWER_FLAG_SYSTEM:     Hardware is system (not runtime) suspended
- * @IPA_POWER_FLAG_STOPPED:    Modem TX is disabled by ipa_start_xmit()
- * @IPA_POWER_FLAG_STARTED:    Modem TX was enabled by ipa_runtime_resume()
- * @IPA_POWER_FLAG_COUNT:      Number of defined power flags
- */
-enum ipa_power_flag {
-       IPA_POWER_FLAG_RESUMED,
-       IPA_POWER_FLAG_SYSTEM,
-       IPA_POWER_FLAG_STOPPED,
-       IPA_POWER_FLAG_STARTED,
-       IPA_POWER_FLAG_COUNT,           /* Last; not a flag */
-};
-
-/**
- * struct ipa_power - IPA power management information
- * @dev:               IPA device pointer
- * @core:              IPA core clock
- * @spinlock:          Protects modem TX queue enable/disable
- * @flags:             Boolean state flags
- * @interconnect_count:        Number of elements in interconnect[]
- * @interconnect:      Interconnect array
- */
-struct ipa_power {
-       struct device *dev;
-       struct clk *core;
-       spinlock_t spinlock;    /* used with STOPPED/STARTED power flags */
-       DECLARE_BITMAP(flags, IPA_POWER_FLAG_COUNT);
-       u32 interconnect_count;
-       struct ipa_interconnect *interconnect;
-};
-
-static int ipa_interconnect_init_one(struct device *dev,
-                                    struct ipa_interconnect *interconnect,
-                                    const struct ipa_interconnect_data *data)
-{
-       struct icc_path *path;
-
-       path = of_icc_get(dev, data->name);
-       if (IS_ERR(path)) {
-               int ret = PTR_ERR(path);
-
-               dev_err_probe(dev, ret, "error getting %s interconnect\n",
-                             data->name);
-
-               return ret;
-       }
-
-       interconnect->path = path;
-       interconnect->average_bandwidth = data->average_bandwidth;
-       interconnect->peak_bandwidth = data->peak_bandwidth;
-
-       return 0;
-}
-
-static void ipa_interconnect_exit_one(struct ipa_interconnect *interconnect)
-{
-       icc_put(interconnect->path);
-       memset(interconnect, 0, sizeof(*interconnect));
-}
-
-/* Initialize interconnects required for IPA operation */
-static int ipa_interconnect_init(struct ipa_power *power, struct device *dev,
-                                const struct ipa_interconnect_data *data)
-{
-       struct ipa_interconnect *interconnect;
-       u32 count;
-       int ret;
-
-       count = power->interconnect_count;
-       interconnect = kcalloc(count, sizeof(*interconnect), GFP_KERNEL);
-       if (!interconnect)
-               return -ENOMEM;
-       power->interconnect = interconnect;
-
-       while (count--) {
-               ret = ipa_interconnect_init_one(dev, interconnect, data++);
-               if (ret)
-                       goto out_unwind;
-               interconnect++;
-       }
-
-       return 0;
-
-out_unwind:
-       while (interconnect-- > power->interconnect)
-               ipa_interconnect_exit_one(interconnect);
-       kfree(power->interconnect);
-       power->interconnect = NULL;
-
-       return ret;
-}
-
-/* Inverse of ipa_interconnect_init() */
-static void ipa_interconnect_exit(struct ipa_power *power)
-{
-       struct ipa_interconnect *interconnect;
-
-       interconnect = power->interconnect + power->interconnect_count;
-       while (interconnect-- > power->interconnect)
-               ipa_interconnect_exit_one(interconnect);
-       kfree(power->interconnect);
-       power->interconnect = NULL;
-}
-
-/* Currently we only use one bandwidth level, so just "enable" interconnects */
-static int ipa_interconnect_enable(struct ipa *ipa)
-{
-       struct ipa_interconnect *interconnect;
-       struct ipa_power *power = ipa->power;
-       int ret;
-       u32 i;
-
-       interconnect = power->interconnect;
-       for (i = 0; i < power->interconnect_count; i++) {
-               ret = icc_set_bw(interconnect->path,
-                                interconnect->average_bandwidth,
-                                interconnect->peak_bandwidth);
-               if (ret) {
-                       dev_err(&ipa->pdev->dev,
-                               "error %d enabling %s interconnect\n",
-                               ret, icc_get_name(interconnect->path));
-                       goto out_unwind;
-               }
-               interconnect++;
-       }
-
-       return 0;
-
-out_unwind:
-       while (interconnect-- > power->interconnect)
-               (void)icc_set_bw(interconnect->path, 0, 0);
-
-       return ret;
-}
-
-/* To disable an interconnect, we just its bandwidth to 0 */
-static int ipa_interconnect_disable(struct ipa *ipa)
-{
-       struct ipa_interconnect *interconnect;
-       struct ipa_power *power = ipa->power;
-       struct device *dev = &ipa->pdev->dev;
-       int result = 0;
-       u32 count;
-       int ret;
-
-       count = power->interconnect_count;
-       interconnect = power->interconnect + count;
-       while (count--) {
-               interconnect--;
-               ret = icc_set_bw(interconnect->path, 0, 0);
-               if (ret) {
-                       dev_err(dev, "error %d disabling %s interconnect\n",
-                               ret, icc_get_name(interconnect->path));
-                       /* Try to disable all; record only the first error */
-                       if (!result)
-                               result = ret;
-               }
-       }
-
-       return result;
-}
-
-/* Enable IPA power, enabling interconnects and the core clock */
-static int ipa_power_enable(struct ipa *ipa)
-{
-       int ret;
-
-       ret = ipa_interconnect_enable(ipa);
-       if (ret)
-               return ret;
-
-       ret = clk_prepare_enable(ipa->power->core);
-       if (ret) {
-               dev_err(&ipa->pdev->dev, "error %d enabling core clock\n", ret);
-               (void)ipa_interconnect_disable(ipa);
-       }
-
-       return ret;
-}
-
-/* Inverse of ipa_power_enable() */
-static int ipa_power_disable(struct ipa *ipa)
-{
-       clk_disable_unprepare(ipa->power->core);
-
-       return ipa_interconnect_disable(ipa);
-}
-
-static int ipa_runtime_suspend(struct device *dev)
-{
-       struct ipa *ipa = dev_get_drvdata(dev);
-
-       /* Endpoints aren't usable until setup is complete */
-       if (ipa->setup_complete) {
-               __clear_bit(IPA_POWER_FLAG_RESUMED, ipa->power->flags);
-               ipa_endpoint_suspend(ipa);
-               gsi_suspend(&ipa->gsi);
-       }
-
-       return ipa_power_disable(ipa);
-}
-
-static int ipa_runtime_resume(struct device *dev)
-{
-       struct ipa *ipa = dev_get_drvdata(dev);
-       int ret;
-
-       ret = ipa_power_enable(ipa);
-       if (WARN_ON(ret < 0))
-               return ret;
-
-       /* Endpoints aren't usable until setup is complete */
-       if (ipa->setup_complete) {
-               gsi_resume(&ipa->gsi);
-               ipa_endpoint_resume(ipa);
-       }
-
-       return 0;
-}
-
-static int ipa_suspend(struct device *dev)
-{
-       struct ipa *ipa = dev_get_drvdata(dev);
-
-       __set_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags);
-
-       return pm_runtime_force_suspend(dev);
-}
-
-static int ipa_resume(struct device *dev)
-{
-       struct ipa *ipa = dev_get_drvdata(dev);
-       int ret;
-
-       ret = pm_runtime_force_resume(dev);
-
-       __clear_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags);
-
-       return ret;
-}
-
-/* Return the current IPA core clock rate */
-u32 ipa_core_clock_rate(struct ipa *ipa)
-{
-       return ipa->power ? (u32)clk_get_rate(ipa->power->core) : 0;
-}
-
-/**
- * ipa_suspend_handler() - Handle the suspend IPA interrupt
- * @ipa:       IPA pointer
- * @irq_id:    IPA interrupt type (unused)
- *
- * If an RX endpoint is suspended, and the IPA has a packet destined for
- * that endpoint, the IPA generates a SUSPEND interrupt to inform the AP
- * that it should resume the endpoint.  If we get one of these interrupts
- * we just wake up the system.
- */
-static void ipa_suspend_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
-{
-       /* To handle an IPA interrupt we will have resumed the hardware
-        * just to handle the interrupt, so we're done.  If we are in a
-        * system suspend, trigger a system resume.
-        */
-       if (!__test_and_set_bit(IPA_POWER_FLAG_RESUMED, ipa->power->flags))
-               if (test_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags))
-                       pm_wakeup_dev_event(&ipa->pdev->dev, 0, true);
-
-       /* Acknowledge/clear the suspend interrupt on all endpoints */
-       ipa_interrupt_suspend_clear_all(ipa->interrupt);
-}
-
-/* The next few functions coordinate stopping and starting the modem
- * network device transmit queue.
- *
- * Transmit can be running concurrent with power resume, and there's a
- * chance the resume completes before the transmit path stops the queue,
- * leaving the queue in a stopped state.  The next two functions are used
- * to avoid this: ipa_power_modem_queue_stop() is used by ipa_start_xmit()
- * to conditionally stop the TX queue; and ipa_power_modem_queue_start()
- * is used by ipa_runtime_resume() to conditionally restart it.
- *
- * Two flags and a spinlock are used.  If the queue is stopped, the STOPPED
- * power flag is set.  And if the queue is started, the STARTED flag is set.
- * The queue is only started on resume if the STOPPED flag is set.  And the
- * queue is only started in ipa_start_xmit() if the STARTED flag is *not*
- * set.  As a result, the queue remains operational if the two activites
- * happen concurrently regardless of the order they complete.  The spinlock
- * ensures the flag and TX queue operations are done atomically.
- *
- * The first function stops the modem netdev transmit queue, but only if
- * the STARTED flag is *not* set.  That flag is cleared if it was set.
- * If the queue is stopped, the STOPPED flag is set.  This is called only
- * from the power ->runtime_resume operation.
- */
-void ipa_power_modem_queue_stop(struct ipa *ipa)
-{
-       struct ipa_power *power = ipa->power;
-       unsigned long flags;
-
-       spin_lock_irqsave(&power->spinlock, flags);
-
-       if (!__test_and_clear_bit(IPA_POWER_FLAG_STARTED, power->flags)) {
-               netif_stop_queue(ipa->modem_netdev);
-               __set_bit(IPA_POWER_FLAG_STOPPED, power->flags);
-       }
-
-       spin_unlock_irqrestore(&power->spinlock, flags);
-}
-
-/* This function starts the modem netdev transmit queue, but only if the
- * STOPPED flag is set.  That flag is cleared if it was set.  If the queue
- * was restarted, the STARTED flag is set; this allows ipa_start_xmit()
- * to skip stopping the queue in the event of a race.
- */
-void ipa_power_modem_queue_wake(struct ipa *ipa)
-{
-       struct ipa_power *power = ipa->power;
-       unsigned long flags;
-
-       spin_lock_irqsave(&power->spinlock, flags);
-
-       if (__test_and_clear_bit(IPA_POWER_FLAG_STOPPED, power->flags)) {
-               __set_bit(IPA_POWER_FLAG_STARTED, power->flags);
-               netif_wake_queue(ipa->modem_netdev);
-       }
-
-       spin_unlock_irqrestore(&power->spinlock, flags);
-}
-
-/* This function clears the STARTED flag once the TX queue is operating */
-void ipa_power_modem_queue_active(struct ipa *ipa)
-{
-       clear_bit(IPA_POWER_FLAG_STARTED, ipa->power->flags);
-}
-
-int ipa_power_setup(struct ipa *ipa)
-{
-       int ret;
-
-       ipa_interrupt_add(ipa->interrupt, IPA_IRQ_TX_SUSPEND,
-                         ipa_suspend_handler);
-
-       ret = device_init_wakeup(&ipa->pdev->dev, true);
-       if (ret)
-               ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND);
-
-       return ret;
-}
-
-void ipa_power_teardown(struct ipa *ipa)
-{
-       (void)device_init_wakeup(&ipa->pdev->dev, false);
-       ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND);
-}
-
-/* Initialize IPA power management */
-struct ipa_power *
-ipa_power_init(struct device *dev, const struct ipa_power_data *data)
-{
-       struct ipa_power *power;
-       struct clk *clk;
-       int ret;
-
-       clk = clk_get(dev, "core");
-       if (IS_ERR(clk)) {
-               dev_err_probe(dev, PTR_ERR(clk), "error getting core clock\n");
-
-               return ERR_CAST(clk);
-       }
-
-       ret = clk_set_rate(clk, data->core_clock_rate);
-       if (ret) {
-               dev_err(dev, "error %d setting core clock rate to %u\n",
-                       ret, data->core_clock_rate);
-               goto err_clk_put;
-       }
-
-       power = kzalloc(sizeof(*power), GFP_KERNEL);
-       if (!power) {
-               ret = -ENOMEM;
-               goto err_clk_put;
-       }
-       power->dev = dev;
-       power->core = clk;
-       spin_lock_init(&power->spinlock);
-       power->interconnect_count = data->interconnect_count;
-
-       ret = ipa_interconnect_init(power, dev, data->interconnect_data);
-       if (ret)
-               goto err_kfree;
-
-       pm_runtime_set_autosuspend_delay(dev, IPA_AUTOSUSPEND_DELAY);
-       pm_runtime_use_autosuspend(dev);
-       pm_runtime_enable(dev);
-
-       return power;
-
-err_kfree:
-       kfree(power);
-err_clk_put:
-       clk_put(clk);
-
-       return ERR_PTR(ret);
-}
-
-/* Inverse of ipa_power_init() */
-void ipa_power_exit(struct ipa_power *power)
-{
-       struct device *dev = power->dev;
-       struct clk *clk = power->core;
-
-       pm_runtime_disable(dev);
-       pm_runtime_dont_use_autosuspend(dev);
-       ipa_interconnect_exit(power);
-       kfree(power);
-       clk_put(clk);
-}
-
-const struct dev_pm_ops ipa_pm_ops = {
-       .suspend                = ipa_suspend,
-       .resume                 = ipa_resume,
-       .runtime_suspend        = ipa_runtime_suspend,
-       .runtime_resume         = ipa_runtime_resume,
-};
diff --git a/drivers/net/ipa/ipa_clock.h b/drivers/net/ipa/ipa_clock.h
deleted file mode 100644 (file)
index 7a6a910..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
- * Copyright (C) 2018-2020 Linaro Ltd.
- */
-#ifndef _IPA_CLOCK_H_
-#define _IPA_CLOCK_H_
-
-struct device;
-
-struct ipa;
-struct ipa_power_data;
-
-/* IPA device power management function block */
-extern const struct dev_pm_ops ipa_pm_ops;
-
-/**
- * ipa_core_clock_rate() - Return the current IPA core clock rate
- * @ipa:       IPA structure
- *
- * Return: The current clock rate (in Hz), or 0.
- */
-u32 ipa_core_clock_rate(struct ipa *ipa);
-
-/**
- * ipa_power_modem_queue_stop() - Possibly stop the modem netdev TX queue
- * @ipa:       IPA pointer
- */
-void ipa_power_modem_queue_stop(struct ipa *ipa);
-
-/**
- * ipa_power_modem_queue_wake() - Possibly wake the modem netdev TX queue
- * @ipa:       IPA pointer
- */
-void ipa_power_modem_queue_wake(struct ipa *ipa);
-
-/**
- * ipa_power_modem_queue_active() - Report modem netdev TX queue active
- * @ipa:       IPA pointer
- */
-void ipa_power_modem_queue_active(struct ipa *ipa);
-
-/**
- * ipa_power_setup() - Set up IPA power management
- * @ipa:       IPA pointer
- *
- * Return:     0 if successful, or a negative error code
- */
-int ipa_power_setup(struct ipa *ipa);
-
-/**
- * ipa_power_teardown() - Inverse of ipa_power_setup()
- * @ipa:       IPA pointer
- */
-void ipa_power_teardown(struct ipa *ipa);
-
-/**
- * ipa_power_init() - Initialize IPA power management
- * @dev:       IPA device
- * @data:      Clock configuration data
- *
- * Return:     A pointer to an ipa_power structure, or a pointer-coded error
- */
-struct ipa_power *ipa_power_init(struct device *dev,
-                                const struct ipa_power_data *data);
-
-/**
- * ipa_power_exit() - Inverse of ipa_power_init()
- * @power:     IPA power pointer
- */
-void ipa_power_exit(struct ipa_power *power);
-
-#endif /* _IPA_CLOCK_H_ */
index f88b43d..5528d97 100644 (file)
@@ -21,7 +21,7 @@
 #include "ipa_modem.h"
 #include "ipa_table.h"
 #include "ipa_gsi.h"
-#include "ipa_clock.h"
+#include "ipa_power.h"
 
 #define atomic_dec_not_zero(v) atomic_add_unless((v), -1, 0)
 
index c8d9c6d..cdfa98a 100644 (file)
@@ -20,7 +20,7 @@
 #include <linux/soc/qcom/mdt_loader.h>
 
 #include "ipa.h"
-#include "ipa_clock.h"
+#include "ipa_power.h"
 #include "ipa_data.h"
 #include "ipa_endpoint.h"
 #include "ipa_resource.h"
index 2ed8085..ad116bc 100644 (file)
@@ -21,7 +21,7 @@
 #include "ipa_smp2p.h"
 #include "ipa_qmi.h"
 #include "ipa_uc.h"
-#include "ipa_clock.h"
+#include "ipa_power.h"
 
 #define IPA_NETDEV_NAME                "rmnet_ipa%d"
 #define IPA_NETDEV_TAILROOM    0       /* for padding by mux layer */
diff --git a/drivers/net/ipa/ipa_power.c b/drivers/net/ipa/ipa_power.c
new file mode 100644 (file)
index 0000000..b1c6c0f
--- /dev/null
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018-2021 Linaro Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/bitops.h>
+
+#include "ipa.h"
+#include "ipa_power.h"
+#include "ipa_endpoint.h"
+#include "ipa_modem.h"
+#include "ipa_data.h"
+
+/**
+ * DOC: IPA Power Management
+ *
+ * The IPA hardware is enabled when the IPA core clock and all the
+ * interconnects (buses) it depends on are enabled.  Runtime power
+ * management is used to determine whether the core clock and
+ * interconnects are enabled, and if not in use to be suspended
+ * automatically.
+ *
+ * The core clock currently runs at a fixed clock rate when enabled,
+ * an all interconnects use a fixed average and peak bandwidth.
+ */
+
+#define IPA_AUTOSUSPEND_DELAY  500     /* milliseconds */
+
+/**
+ * struct ipa_interconnect - IPA interconnect information
+ * @path:              Interconnect path
+ * @average_bandwidth: Average interconnect bandwidth (KB/second)
+ * @peak_bandwidth:    Peak interconnect bandwidth (KB/second)
+ */
+struct ipa_interconnect {
+       struct icc_path *path;
+       u32 average_bandwidth;
+       u32 peak_bandwidth;
+};
+
+/**
+ * enum ipa_power_flag - IPA power flags
+ * @IPA_POWER_FLAG_RESUMED:    Whether resume from suspend has been signaled
+ * @IPA_POWER_FLAG_SYSTEM:     Hardware is system (not runtime) suspended
+ * @IPA_POWER_FLAG_STOPPED:    Modem TX is disabled by ipa_start_xmit()
+ * @IPA_POWER_FLAG_STARTED:    Modem TX was enabled by ipa_runtime_resume()
+ * @IPA_POWER_FLAG_COUNT:      Number of defined power flags
+ */
+enum ipa_power_flag {
+       IPA_POWER_FLAG_RESUMED,
+       IPA_POWER_FLAG_SYSTEM,
+       IPA_POWER_FLAG_STOPPED,
+       IPA_POWER_FLAG_STARTED,
+       IPA_POWER_FLAG_COUNT,           /* Last; not a flag */
+};
+
+/**
+ * struct ipa_power - IPA power management information
+ * @dev:               IPA device pointer
+ * @core:              IPA core clock
+ * @spinlock:          Protects modem TX queue enable/disable
+ * @flags:             Boolean state flags
+ * @interconnect_count:        Number of elements in interconnect[]
+ * @interconnect:      Interconnect array
+ */
+struct ipa_power {
+       struct device *dev;
+       struct clk *core;
+       spinlock_t spinlock;    /* used with STOPPED/STARTED power flags */
+       DECLARE_BITMAP(flags, IPA_POWER_FLAG_COUNT);
+       u32 interconnect_count;
+       struct ipa_interconnect *interconnect;
+};
+
+static int ipa_interconnect_init_one(struct device *dev,
+                                    struct ipa_interconnect *interconnect,
+                                    const struct ipa_interconnect_data *data)
+{
+       struct icc_path *path;
+
+       path = of_icc_get(dev, data->name);
+       if (IS_ERR(path)) {
+               int ret = PTR_ERR(path);
+
+               dev_err_probe(dev, ret, "error getting %s interconnect\n",
+                             data->name);
+
+               return ret;
+       }
+
+       interconnect->path = path;
+       interconnect->average_bandwidth = data->average_bandwidth;
+       interconnect->peak_bandwidth = data->peak_bandwidth;
+
+       return 0;
+}
+
+static void ipa_interconnect_exit_one(struct ipa_interconnect *interconnect)
+{
+       icc_put(interconnect->path);
+       memset(interconnect, 0, sizeof(*interconnect));
+}
+
+/* Initialize interconnects required for IPA operation */
+static int ipa_interconnect_init(struct ipa_power *power, struct device *dev,
+                                const struct ipa_interconnect_data *data)
+{
+       struct ipa_interconnect *interconnect;
+       u32 count;
+       int ret;
+
+       count = power->interconnect_count;
+       interconnect = kcalloc(count, sizeof(*interconnect), GFP_KERNEL);
+       if (!interconnect)
+               return -ENOMEM;
+       power->interconnect = interconnect;
+
+       while (count--) {
+               ret = ipa_interconnect_init_one(dev, interconnect, data++);
+               if (ret)
+                       goto out_unwind;
+               interconnect++;
+       }
+
+       return 0;
+
+out_unwind:
+       while (interconnect-- > power->interconnect)
+               ipa_interconnect_exit_one(interconnect);
+       kfree(power->interconnect);
+       power->interconnect = NULL;
+
+       return ret;
+}
+
+/* Inverse of ipa_interconnect_init() */
+static void ipa_interconnect_exit(struct ipa_power *power)
+{
+       struct ipa_interconnect *interconnect;
+
+       interconnect = power->interconnect + power->interconnect_count;
+       while (interconnect-- > power->interconnect)
+               ipa_interconnect_exit_one(interconnect);
+       kfree(power->interconnect);
+       power->interconnect = NULL;
+}
+
+/* Currently we only use one bandwidth level, so just "enable" interconnects */
+static int ipa_interconnect_enable(struct ipa *ipa)
+{
+       struct ipa_interconnect *interconnect;
+       struct ipa_power *power = ipa->power;
+       int ret;
+       u32 i;
+
+       interconnect = power->interconnect;
+       for (i = 0; i < power->interconnect_count; i++) {
+               ret = icc_set_bw(interconnect->path,
+                                interconnect->average_bandwidth,
+                                interconnect->peak_bandwidth);
+               if (ret) {
+                       dev_err(&ipa->pdev->dev,
+                               "error %d enabling %s interconnect\n",
+                               ret, icc_get_name(interconnect->path));
+                       goto out_unwind;
+               }
+               interconnect++;
+       }
+
+       return 0;
+
+out_unwind:
+       while (interconnect-- > power->interconnect)
+               (void)icc_set_bw(interconnect->path, 0, 0);
+
+       return ret;
+}
+
+/* To disable an interconnect, we just its bandwidth to 0 */
+static int ipa_interconnect_disable(struct ipa *ipa)
+{
+       struct ipa_interconnect *interconnect;
+       struct ipa_power *power = ipa->power;
+       struct device *dev = &ipa->pdev->dev;
+       int result = 0;
+       u32 count;
+       int ret;
+
+       count = power->interconnect_count;
+       interconnect = power->interconnect + count;
+       while (count--) {
+               interconnect--;
+               ret = icc_set_bw(interconnect->path, 0, 0);
+               if (ret) {
+                       dev_err(dev, "error %d disabling %s interconnect\n",
+                               ret, icc_get_name(interconnect->path));
+                       /* Try to disable all; record only the first error */
+                       if (!result)
+                               result = ret;
+               }
+       }
+
+       return result;
+}
+
+/* Enable IPA power, enabling interconnects and the core clock */
+static int ipa_power_enable(struct ipa *ipa)
+{
+       int ret;
+
+       ret = ipa_interconnect_enable(ipa);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(ipa->power->core);
+       if (ret) {
+               dev_err(&ipa->pdev->dev, "error %d enabling core clock\n", ret);
+               (void)ipa_interconnect_disable(ipa);
+       }
+
+       return ret;
+}
+
+/* Inverse of ipa_power_enable() */
+static int ipa_power_disable(struct ipa *ipa)
+{
+       clk_disable_unprepare(ipa->power->core);
+
+       return ipa_interconnect_disable(ipa);
+}
+
+static int ipa_runtime_suspend(struct device *dev)
+{
+       struct ipa *ipa = dev_get_drvdata(dev);
+
+       /* Endpoints aren't usable until setup is complete */
+       if (ipa->setup_complete) {
+               __clear_bit(IPA_POWER_FLAG_RESUMED, ipa->power->flags);
+               ipa_endpoint_suspend(ipa);
+               gsi_suspend(&ipa->gsi);
+       }
+
+       return ipa_power_disable(ipa);
+}
+
+static int ipa_runtime_resume(struct device *dev)
+{
+       struct ipa *ipa = dev_get_drvdata(dev);
+       int ret;
+
+       ret = ipa_power_enable(ipa);
+       if (WARN_ON(ret < 0))
+               return ret;
+
+       /* Endpoints aren't usable until setup is complete */
+       if (ipa->setup_complete) {
+               gsi_resume(&ipa->gsi);
+               ipa_endpoint_resume(ipa);
+       }
+
+       return 0;
+}
+
+static int ipa_suspend(struct device *dev)
+{
+       struct ipa *ipa = dev_get_drvdata(dev);
+
+       __set_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags);
+
+       return pm_runtime_force_suspend(dev);
+}
+
+static int ipa_resume(struct device *dev)
+{
+       struct ipa *ipa = dev_get_drvdata(dev);
+       int ret;
+
+       ret = pm_runtime_force_resume(dev);
+
+       __clear_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags);
+
+       return ret;
+}
+
+/* Return the current IPA core clock rate */
+u32 ipa_core_clock_rate(struct ipa *ipa)
+{
+       return ipa->power ? (u32)clk_get_rate(ipa->power->core) : 0;
+}
+
+/**
+ * ipa_suspend_handler() - Handle the suspend IPA interrupt
+ * @ipa:       IPA pointer
+ * @irq_id:    IPA interrupt type (unused)
+ *
+ * If an RX endpoint is suspended, and the IPA has a packet destined for
+ * that endpoint, the IPA generates a SUSPEND interrupt to inform the AP
+ * that it should resume the endpoint.  If we get one of these interrupts
+ * we just wake up the system.
+ */
+static void ipa_suspend_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
+{
+       /* To handle an IPA interrupt we will have resumed the hardware
+        * just to handle the interrupt, so we're done.  If we are in a
+        * system suspend, trigger a system resume.
+        */
+       if (!__test_and_set_bit(IPA_POWER_FLAG_RESUMED, ipa->power->flags))
+               if (test_bit(IPA_POWER_FLAG_SYSTEM, ipa->power->flags))
+                       pm_wakeup_dev_event(&ipa->pdev->dev, 0, true);
+
+       /* Acknowledge/clear the suspend interrupt on all endpoints */
+       ipa_interrupt_suspend_clear_all(ipa->interrupt);
+}
+
+/* The next few functions coordinate stopping and starting the modem
+ * network device transmit queue.
+ *
+ * Transmit can be running concurrent with power resume, and there's a
+ * chance the resume completes before the transmit path stops the queue,
+ * leaving the queue in a stopped state.  The next two functions are used
+ * to avoid this: ipa_power_modem_queue_stop() is used by ipa_start_xmit()
+ * to conditionally stop the TX queue; and ipa_power_modem_queue_start()
+ * is used by ipa_runtime_resume() to conditionally restart it.
+ *
+ * Two flags and a spinlock are used.  If the queue is stopped, the STOPPED
+ * power flag is set.  And if the queue is started, the STARTED flag is set.
+ * The queue is only started on resume if the STOPPED flag is set.  And the
+ * queue is only started in ipa_start_xmit() if the STARTED flag is *not*
+ * set.  As a result, the queue remains operational if the two activites
+ * happen concurrently regardless of the order they complete.  The spinlock
+ * ensures the flag and TX queue operations are done atomically.
+ *
+ * The first function stops the modem netdev transmit queue, but only if
+ * the STARTED flag is *not* set.  That flag is cleared if it was set.
+ * If the queue is stopped, the STOPPED flag is set.  This is called only
+ * from the power ->runtime_resume operation.
+ */
+void ipa_power_modem_queue_stop(struct ipa *ipa)
+{
+       struct ipa_power *power = ipa->power;
+       unsigned long flags;
+
+       spin_lock_irqsave(&power->spinlock, flags);
+
+       if (!__test_and_clear_bit(IPA_POWER_FLAG_STARTED, power->flags)) {
+               netif_stop_queue(ipa->modem_netdev);
+               __set_bit(IPA_POWER_FLAG_STOPPED, power->flags);
+       }
+
+       spin_unlock_irqrestore(&power->spinlock, flags);
+}
+
+/* This function starts the modem netdev transmit queue, but only if the
+ * STOPPED flag is set.  That flag is cleared if it was set.  If the queue
+ * was restarted, the STARTED flag is set; this allows ipa_start_xmit()
+ * to skip stopping the queue in the event of a race.
+ */
+void ipa_power_modem_queue_wake(struct ipa *ipa)
+{
+       struct ipa_power *power = ipa->power;
+       unsigned long flags;
+
+       spin_lock_irqsave(&power->spinlock, flags);
+
+       if (__test_and_clear_bit(IPA_POWER_FLAG_STOPPED, power->flags)) {
+               __set_bit(IPA_POWER_FLAG_STARTED, power->flags);
+               netif_wake_queue(ipa->modem_netdev);
+       }
+
+       spin_unlock_irqrestore(&power->spinlock, flags);
+}
+
+/* This function clears the STARTED flag once the TX queue is operating */
+void ipa_power_modem_queue_active(struct ipa *ipa)
+{
+       clear_bit(IPA_POWER_FLAG_STARTED, ipa->power->flags);
+}
+
+int ipa_power_setup(struct ipa *ipa)
+{
+       int ret;
+
+       ipa_interrupt_add(ipa->interrupt, IPA_IRQ_TX_SUSPEND,
+                         ipa_suspend_handler);
+
+       ret = device_init_wakeup(&ipa->pdev->dev, true);
+       if (ret)
+               ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND);
+
+       return ret;
+}
+
+void ipa_power_teardown(struct ipa *ipa)
+{
+       (void)device_init_wakeup(&ipa->pdev->dev, false);
+       ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND);
+}
+
+/* Initialize IPA power management */
+struct ipa_power *
+ipa_power_init(struct device *dev, const struct ipa_power_data *data)
+{
+       struct ipa_power *power;
+       struct clk *clk;
+       int ret;
+
+       clk = clk_get(dev, "core");
+       if (IS_ERR(clk)) {
+               dev_err_probe(dev, PTR_ERR(clk), "error getting core clock\n");
+
+               return ERR_CAST(clk);
+       }
+
+       ret = clk_set_rate(clk, data->core_clock_rate);
+       if (ret) {
+               dev_err(dev, "error %d setting core clock rate to %u\n",
+                       ret, data->core_clock_rate);
+               goto err_clk_put;
+       }
+
+       power = kzalloc(sizeof(*power), GFP_KERNEL);
+       if (!power) {
+               ret = -ENOMEM;
+               goto err_clk_put;
+       }
+       power->dev = dev;
+       power->core = clk;
+       spin_lock_init(&power->spinlock);
+       power->interconnect_count = data->interconnect_count;
+
+       ret = ipa_interconnect_init(power, dev, data->interconnect_data);
+       if (ret)
+               goto err_kfree;
+
+       pm_runtime_set_autosuspend_delay(dev, IPA_AUTOSUSPEND_DELAY);
+       pm_runtime_use_autosuspend(dev);
+       pm_runtime_enable(dev);
+
+       return power;
+
+err_kfree:
+       kfree(power);
+err_clk_put:
+       clk_put(clk);
+
+       return ERR_PTR(ret);
+}
+
+/* Inverse of ipa_power_init() */
+void ipa_power_exit(struct ipa_power *power)
+{
+       struct device *dev = power->dev;
+       struct clk *clk = power->core;
+
+       pm_runtime_disable(dev);
+       pm_runtime_dont_use_autosuspend(dev);
+       ipa_interconnect_exit(power);
+       kfree(power);
+       clk_put(clk);
+}
+
+const struct dev_pm_ops ipa_pm_ops = {
+       .suspend                = ipa_suspend,
+       .resume                 = ipa_resume,
+       .runtime_suspend        = ipa_runtime_suspend,
+       .runtime_resume         = ipa_runtime_resume,
+};
diff --git a/drivers/net/ipa/ipa_power.h b/drivers/net/ipa/ipa_power.h
new file mode 100644 (file)
index 0000000..2151805
--- /dev/null
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
+ * Copyright (C) 2018-2020 Linaro Ltd.
+ */
+#ifndef _IPA_POWER_H_
+#define _IPA_POWER_H_
+
+struct device;
+
+struct ipa;
+struct ipa_power_data;
+
+/* IPA device power management function block */
+extern const struct dev_pm_ops ipa_pm_ops;
+
+/**
+ * ipa_core_clock_rate() - Return the current IPA core clock rate
+ * @ipa:       IPA structure
+ *
+ * Return: The current clock rate (in Hz), or 0.
+ */
+u32 ipa_core_clock_rate(struct ipa *ipa);
+
+/**
+ * ipa_power_modem_queue_stop() - Possibly stop the modem netdev TX queue
+ * @ipa:       IPA pointer
+ */
+void ipa_power_modem_queue_stop(struct ipa *ipa);
+
+/**
+ * ipa_power_modem_queue_wake() - Possibly wake the modem netdev TX queue
+ * @ipa:       IPA pointer
+ */
+void ipa_power_modem_queue_wake(struct ipa *ipa);
+
+/**
+ * ipa_power_modem_queue_active() - Report modem netdev TX queue active
+ * @ipa:       IPA pointer
+ */
+void ipa_power_modem_queue_active(struct ipa *ipa);
+
+/**
+ * ipa_power_setup() - Set up IPA power management
+ * @ipa:       IPA pointer
+ *
+ * Return:     0 if successful, or a negative error code
+ */
+int ipa_power_setup(struct ipa *ipa);
+
+/**
+ * ipa_power_teardown() - Inverse of ipa_power_setup()
+ * @ipa:       IPA pointer
+ */
+void ipa_power_teardown(struct ipa *ipa);
+
+/**
+ * ipa_power_init() - Initialize IPA power management
+ * @dev:       IPA device
+ * @data:      Clock configuration data
+ *
+ * Return:     A pointer to an ipa_power structure, or a pointer-coded error
+ */
+struct ipa_power *ipa_power_init(struct device *dev,
+                                const struct ipa_power_data *data);
+
+/**
+ * ipa_power_exit() - Inverse of ipa_power_init()
+ * @power:     IPA power pointer
+ */
+void ipa_power_exit(struct ipa_power *power);
+
+#endif /* _IPA_POWER_H_ */