diff --git a/drivers/net/ipa/ipa.h b/drivers/net/ipa/ipa.h index 71ba996096bb..34152fe02963 100644 --- a/drivers/net/ipa/ipa.h +++ b/drivers/net/ipa/ipa.h @@ -27,20 +27,9 @@ struct ipa_clock; struct ipa_smp2p; struct ipa_interrupt; -/** - * enum ipa_flag - IPA state flags - * @IPA_FLAG_RESUMED: Whether resume from suspend has been signaled - * @IPA_FLAG_COUNT: Number of defined IPA flags - */ -enum ipa_flag { - IPA_FLAG_RESUMED, - IPA_FLAG_COUNT, /* Last; not a flag */ -}; - /** * struct ipa - IPA information * @gsi: Embedded GSI structure - * @flags: Boolean state flags * @version: IPA hardware version * @pdev: Platform device * @completion: Used to signal pipeline clear transfer complete @@ -83,7 +72,6 @@ enum ipa_flag { */ struct ipa { struct gsi gsi; - DECLARE_BITMAP(flags, IPA_FLAG_COUNT); enum ipa_version version; struct platform_device *pdev; struct completion completion; diff --git a/drivers/net/ipa/ipa_clock.c b/drivers/net/ipa/ipa_clock.c index 69ef6ea41e61..a67b6136e3c0 100644 --- a/drivers/net/ipa/ipa_clock.c +++ b/drivers/net/ipa/ipa_clock.c @@ -9,9 +9,12 @@ #include #include #include +#include +#include #include "ipa.h" #include "ipa_clock.h" +#include "ipa_endpoint.h" #include "ipa_modem.h" #include "ipa_data.h" @@ -42,11 +45,22 @@ struct ipa_interconnect { u32 peak_bandwidth; }; +/** + * enum ipa_power_flag - IPA power flags + * @IPA_POWER_FLAG_RESUMED: Whether resume from suspend has been signaled + * @IPA_POWER_FLAG_COUNT: Number of defined power flags + */ +enum ipa_power_flag { + IPA_POWER_FLAG_RESUMED, + IPA_POWER_FLAG_COUNT, /* Last; not a flag */ +}; + /** * struct ipa_clock - IPA clocking information * @count: Clocking reference count * @mutex: Protects clock enable/disable * @core: IPA core clock + * @flags: Boolean state flags * @interconnect_count: Number of elements in interconnect[] * @interconnect: Interconnect array */ @@ -54,6 +68,7 @@ struct ipa_clock { refcount_t count; struct mutex mutex; /* protects clock enable/disable */ struct clk *core; + DECLARE_BITMAP(flags, IPA_POWER_FLAG_COUNT); u32 interconnect_count; struct ipa_interconnect *interconnect; }; @@ -144,8 +159,12 @@ static int ipa_interconnect_enable(struct ipa *ipa) ret = icc_set_bw(interconnect->path, interconnect->average_bandwidth, interconnect->peak_bandwidth); - if (ret) + if (ret) { + dev_err(&ipa->pdev->dev, + "error %d enabling %s interconnect\n", + ret, icc_get_name(interconnect->path)); goto out_unwind; + } interconnect++; } @@ -159,10 +178,11 @@ out_unwind: } /* To disable an interconnect, we just its bandwidth to 0 */ -static void ipa_interconnect_disable(struct ipa *ipa) +static int ipa_interconnect_disable(struct ipa *ipa) { struct ipa_interconnect *interconnect; struct ipa_clock *clock = ipa->clock; + struct device *dev = &ipa->pdev->dev; int result = 0; u32 count; int ret; @@ -172,13 +192,16 @@ static void ipa_interconnect_disable(struct ipa *ipa) while (count--) { interconnect--; ret = icc_set_bw(interconnect->path, 0, 0); - if (ret && !result) - result = ret; + 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; + } } - if (result) - dev_err(&ipa->pdev->dev, - "error %d disabling IPA interconnects\n", ret); + return result; } /* Turn on IPA clocks, including interconnects */ @@ -191,8 +214,10 @@ static int ipa_clock_enable(struct ipa *ipa) return ret; ret = clk_prepare_enable(ipa->clock->core); - if (ret) - ipa_interconnect_disable(ipa); + if (ret) { + dev_err(&ipa->pdev->dev, "error %d enabling core clock\n", ret); + (void)ipa_interconnect_disable(ipa); + } return ret; } @@ -201,7 +226,7 @@ static int ipa_clock_enable(struct ipa *ipa) static void ipa_clock_disable(struct ipa *ipa) { clk_disable_unprepare(ipa->clock->core); - ipa_interconnect_disable(ipa); + (void)ipa_interconnect_disable(ipa); } /* Get an IPA clock reference, but only if the reference count is @@ -238,13 +263,8 @@ void ipa_clock_get(struct ipa *ipa) goto out_mutex_unlock; ret = ipa_clock_enable(ipa); - if (ret) { - dev_err(&ipa->pdev->dev, "error %d enabling IPA clock\n", ret); - goto out_mutex_unlock; - } - - refcount_set(&clock->count, 1); - + if (!ret) + refcount_set(&clock->count, 1); out_mutex_unlock: mutex_unlock(&clock->mutex); } @@ -271,6 +291,40 @@ u32 ipa_clock_rate(struct ipa *ipa) return ipa->clock ? (u32)clk_get_rate(ipa->clock->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) +{ + /* Just report the event, and let system resume handle the rest. + * More than one endpoint could signal this; if so, ignore + * all but the first. + */ + if (!test_and_set_bit(IPA_POWER_FLAG_RESUMED, ipa->clock->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); +} + +void ipa_power_setup(struct ipa *ipa) +{ + ipa_interrupt_add(ipa->interrupt, IPA_IRQ_TX_SUSPEND, + ipa_suspend_handler); +} + +void ipa_power_teardown(struct ipa *ipa) +{ + ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND); +} + /* Initialize IPA clocking */ struct ipa_clock * ipa_clock_init(struct device *dev, const struct ipa_clock_data *data) @@ -329,3 +383,62 @@ void ipa_clock_exit(struct ipa_clock *clock) kfree(clock); clk_put(clk); } + +/** + * ipa_suspend() - Power management system suspend callback + * @dev: IPA device structure + * + * Return: Always returns zero + * + * Called by the PM framework when a system suspend operation is invoked. + * Suspends endpoints and releases the clock reference held to keep + * the IPA clock running until this point. + */ +static int ipa_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->clock->flags); + ipa_endpoint_suspend(ipa); + gsi_suspend(&ipa->gsi); + } + + ipa_clock_put(ipa); + + return 0; +} + +/** + * ipa_resume() - Power management system resume callback + * @dev: IPA device structure + * + * Return: Always returns 0 + * + * Called by the PM framework when a system resume operation is invoked. + * Takes an IPA clock reference to keep the clock running until suspend, + * and resumes endpoints. + */ +static int ipa_resume(struct device *dev) +{ + struct ipa *ipa = dev_get_drvdata(dev); + + /* This clock reference will keep the IPA out of suspend + * until we get a power management suspend request. + */ + ipa_clock_get(ipa); + + /* Endpoints aren't usable until setup is complete */ + if (ipa->setup_complete) { + gsi_resume(&ipa->gsi); + ipa_endpoint_resume(ipa); + } + + return 0; +} + +const struct dev_pm_ops ipa_pm_ops = { + .suspend = ipa_suspend, + .resume = ipa_resume, +}; diff --git a/drivers/net/ipa/ipa_clock.h b/drivers/net/ipa/ipa_clock.h index 1fe634760e59..2a0f7ff3c9e6 100644 --- a/drivers/net/ipa/ipa_clock.h +++ b/drivers/net/ipa/ipa_clock.h @@ -11,6 +11,9 @@ struct device; struct ipa; struct ipa_clock_data; +/* IPA device power management function block */ +extern const struct dev_pm_ops ipa_pm_ops; + /** * ipa_clock_rate() - Return the current IPA core clock rate * @ipa: IPA structure @@ -19,6 +22,18 @@ struct ipa_clock_data; */ u32 ipa_clock_rate(struct ipa *ipa); +/** + * ipa_power_setup() - Set up IPA power management + * @ipa: IPA pointer + */ +void 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_clock_init() - Initialize IPA clocking * @dev: IPA device diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c index ae51109dea01..25bbb456e007 100644 --- a/drivers/net/ipa/ipa_main.c +++ b/drivers/net/ipa/ipa_main.c @@ -79,29 +79,6 @@ /* Divider for 19.2 MHz crystal oscillator clock to get common timer clock */ #define IPA_XO_CLOCK_DIVIDER 192 /* 1 is subtracted where used */ -/** - * ipa_suspend_handler() - Handle the suspend IPA interrupt - * @ipa: IPA pointer - * @irq_id: IPA interrupt type (unused) - * - * If an RX endpoint is in suspend state, 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 resume everything. - */ -static void ipa_suspend_handler(struct ipa *ipa, enum ipa_irq_id irq_id) -{ - /* Just report the event, and let system resume handle the rest. - * More than one endpoint could signal this; if so, ignore - * all but the first. - */ - if (!test_and_set_bit(IPA_FLAG_RESUMED, ipa->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); -} - /** * ipa_setup() - Set up IPA hardware * @ipa: IPA pointer @@ -124,12 +101,11 @@ int ipa_setup(struct ipa *ipa) if (ret) return ret; - ipa_interrupt_add(ipa->interrupt, IPA_IRQ_TX_SUSPEND, - ipa_suspend_handler); + ipa_power_setup(ipa); ret = device_init_wakeup(dev, true); if (ret) - goto err_interrupt_remove; + goto err_gsi_teardown; ipa_endpoint_setup(ipa); @@ -177,9 +153,9 @@ err_command_disable: ipa_endpoint_disable_one(command_endpoint); err_endpoint_teardown: ipa_endpoint_teardown(ipa); + ipa_power_teardown(ipa); (void)device_init_wakeup(dev, false); -err_interrupt_remove: - ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND); +err_gsi_teardown: gsi_teardown(&ipa->gsi); return ret; @@ -204,8 +180,8 @@ static void ipa_teardown(struct ipa *ipa) command_endpoint = ipa->name_map[IPA_ENDPOINT_AP_COMMAND_TX]; ipa_endpoint_disable_one(command_endpoint); ipa_endpoint_teardown(ipa); + ipa_power_teardown(ipa); (void)device_init_wakeup(&ipa->pdev->dev, false); - ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_TX_SUSPEND); gsi_teardown(&ipa->gsi); } @@ -474,7 +450,7 @@ static int ipa_config(struct ipa *ipa, const struct ipa_data *data) ret = ipa_endpoint_config(ipa); if (ret) - goto err_interrupt_deconfig; + goto err_uc_deconfig; ipa_table_config(ipa); /* No deconfig required */ @@ -491,7 +467,7 @@ static int ipa_config(struct ipa *ipa, const struct ipa_data *data) err_endpoint_deconfig: ipa_endpoint_deconfig(ipa); -err_interrupt_deconfig: +err_uc_deconfig: ipa_uc_deconfig(ipa); ipa_interrupt_deconfig(ipa->interrupt); ipa->interrupt = NULL; @@ -874,65 +850,6 @@ static void ipa_shutdown(struct platform_device *pdev) dev_err(&pdev->dev, "shutdown: remove returned %d\n", ret); } -/** - * ipa_suspend() - Power management system suspend callback - * @dev: IPA device structure - * - * Return: Always returns zero - * - * Called by the PM framework when a system suspend operation is invoked. - * Suspends endpoints and releases the clock reference held to keep - * the IPA clock running until this point. - */ -static int ipa_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_FLAG_RESUMED, ipa->flags); - ipa_endpoint_suspend(ipa); - gsi_suspend(&ipa->gsi); - } - - ipa_clock_put(ipa); - - return 0; -} - -/** - * ipa_resume() - Power management system resume callback - * @dev: IPA device structure - * - * Return: Always returns 0 - * - * Called by the PM framework when a system resume operation is invoked. - * Takes an IPA clock reference to keep the clock running until suspend, - * and resumes endpoints. - */ -static int ipa_resume(struct device *dev) -{ - struct ipa *ipa = dev_get_drvdata(dev); - - /* This clock reference will keep the IPA out of suspend - * until we get a power management suspend request. - */ - ipa_clock_get(ipa); - - /* Endpoints aren't usable until setup is complete */ - if (ipa->setup_complete) { - gsi_resume(&ipa->gsi); - ipa_endpoint_resume(ipa); - } - - return 0; -} - -static const struct dev_pm_ops ipa_pm_ops = { - .suspend = ipa_suspend, - .resume = ipa_resume, -}; - static const struct attribute_group *ipa_attribute_groups[] = { &ipa_attribute_group, &ipa_feature_attribute_group, diff --git a/drivers/net/ipa/ipa_modem.c b/drivers/net/ipa/ipa_modem.c index 4ea8287e9d23..ad4019e8016e 100644 --- a/drivers/net/ipa/ipa_modem.c +++ b/drivers/net/ipa/ipa_modem.c @@ -178,6 +178,9 @@ void ipa_modem_suspend(struct net_device *netdev) struct ipa_priv *priv = netdev_priv(netdev); struct ipa *ipa = priv->ipa; + if (!(netdev->flags & IFF_UP)) + return; + netif_stop_queue(netdev); ipa_endpoint_suspend_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]); @@ -194,6 +197,9 @@ void ipa_modem_resume(struct net_device *netdev) struct ipa_priv *priv = netdev_priv(netdev); struct ipa *ipa = priv->ipa; + if (!(netdev->flags & IFF_UP)) + return; + ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]); ipa_endpoint_resume_one(ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]); @@ -225,13 +231,15 @@ int ipa_modem_start(struct ipa *ipa) SET_NETDEV_DEV(netdev, &ipa->pdev->dev); priv = netdev_priv(netdev); priv->ipa = ipa; + ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]->netdev = netdev; + ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->netdev = netdev; + ipa->modem_netdev = netdev; ret = register_netdev(netdev); - if (!ret) { - ipa->modem_netdev = netdev; - ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]->netdev = netdev; - ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->netdev = netdev; - } else { + if (ret) { + ipa->modem_netdev = NULL; + ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->netdev = NULL; + ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]->netdev = NULL; free_netdev(netdev); } @@ -265,13 +273,15 @@ int ipa_modem_stop(struct ipa *ipa) /* Prevent the modem from triggering a call to ipa_setup() */ ipa_smp2p_disable(ipa); - /* Stop the queue and disable the endpoints if it's open */ + /* Clean up the netdev and endpoints if it was started */ if (netdev) { - (void)ipa_stop(netdev); + /* If it was opened, stop it first */ + if (netdev->flags & IFF_UP) + (void)ipa_stop(netdev); + unregister_netdev(netdev); + ipa->modem_netdev = NULL; ipa->name_map[IPA_ENDPOINT_AP_MODEM_RX]->netdev = NULL; ipa->name_map[IPA_ENDPOINT_AP_MODEM_TX]->netdev = NULL; - ipa->modem_netdev = NULL; - unregister_netdev(netdev); free_netdev(netdev); }