summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/ipa/ipa_clock.c71
-rw-r--r--drivers/net/ipa/ipa_clock.h18
-rw-r--r--drivers/net/ipa/ipa_modem.c7
3 files changed, 94 insertions, 2 deletions
diff --git a/drivers/net/ipa/ipa_clock.c b/drivers/net/ipa/ipa_clock.c
index 8f25107c1f1e..74eb9ecdd19b 100644
--- a/drivers/net/ipa/ipa_clock.c
+++ b/drivers/net/ipa/ipa_clock.c
@@ -48,11 +48,15 @@ struct ipa_interconnect {
* 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 */
};
@@ -60,6 +64,7 @@ enum ipa_power_flag {
* struct ipa_clock - IPA clocking 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
@@ -67,6 +72,7 @@ enum ipa_power_flag {
struct ipa_clock {
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;
@@ -334,6 +340,70 @@ static void ipa_suspend_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
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_clock *clock = ipa->clock;
+ unsigned long flags;
+
+ spin_lock_irqsave(&clock->spinlock, flags);
+
+ if (!__test_and_clear_bit(IPA_POWER_FLAG_STARTED, clock->flags)) {
+ netif_stop_queue(ipa->modem_netdev);
+ __set_bit(IPA_POWER_FLAG_STOPPED, clock->flags);
+ }
+
+ spin_unlock_irqrestore(&clock->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_clock *clock = ipa->clock;
+ unsigned long flags;
+
+ spin_lock_irqsave(&clock->spinlock, flags);
+
+ if (__test_and_clear_bit(IPA_POWER_FLAG_STOPPED, clock->flags)) {
+ __set_bit(IPA_POWER_FLAG_STARTED, clock->flags);
+ netif_wake_queue(ipa->modem_netdev);
+ }
+
+ spin_unlock_irqrestore(&clock->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->clock->flags);
+}
+
int ipa_power_setup(struct ipa *ipa)
{
int ret;
@@ -383,6 +453,7 @@ ipa_clock_init(struct device *dev, const struct ipa_clock_data *data)
}
clock->dev = dev;
clock->core = clk;
+ spin_lock_init(&clock->spinlock);
clock->interconnect_count = data->interconnect_count;
ret = ipa_interconnect_init(clock, dev, data->interconnect_data);
diff --git a/drivers/net/ipa/ipa_clock.h b/drivers/net/ipa/ipa_clock.h
index 5c53241336a1..64cd15981b1d 100644
--- a/drivers/net/ipa/ipa_clock.h
+++ b/drivers/net/ipa/ipa_clock.h
@@ -23,6 +23,24 @@ extern const struct dev_pm_ops ipa_pm_ops;
u32 ipa_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
*
diff --git a/drivers/net/ipa/ipa_modem.c b/drivers/net/ipa/ipa_modem.c
index c8724af935b8..16d87910305e 100644
--- a/drivers/net/ipa/ipa_modem.c
+++ b/drivers/net/ipa/ipa_modem.c
@@ -130,6 +130,7 @@ ipa_start_xmit(struct sk_buff *skb, struct net_device *netdev)
if (ret < 1) {
/* If a resume won't happen, just drop the packet */
if (ret < 0 && ret != -EINPROGRESS) {
+ ipa_power_modem_queue_active(ipa);
pm_runtime_put_noidle(dev);
goto err_drop_skb;
}
@@ -138,13 +139,15 @@ ipa_start_xmit(struct sk_buff *skb, struct net_device *netdev)
* until we're resumed; ipa_modem_resume() arranges for the
* TX queue to be started again.
*/
- netif_stop_queue(netdev);
+ ipa_power_modem_queue_stop(ipa);
(void)pm_runtime_put(dev);
return NETDEV_TX_BUSY;
}
+ ipa_power_modem_queue_active(ipa);
+
ret = ipa_endpoint_skb_tx(endpoint, skb);
(void)pm_runtime_put(dev);
@@ -241,7 +244,7 @@ static void ipa_modem_wake_queue_work(struct work_struct *work)
{
struct ipa_priv *priv = container_of(work, struct ipa_priv, work);
- netif_wake_queue(priv->ipa->modem_netdev);
+ ipa_power_modem_queue_wake(priv->ipa);
}
/** ipa_modem_resume() - resume callback for runtime_pm