| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- // SPDX-License-Identifier: GPL-2.0
- /* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
- * Copyright (C) 2018-2024 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/soc/qcom/qcom_aoss.h"
- #include "ipa.h"
- #include "ipa_data.h"
- #include "ipa_endpoint.h"
- #include "ipa_interrupt.h"
- #include "ipa_modem.h"
- #include "ipa_power.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_power - IPA power management information
- * @dev: IPA device pointer
- * @core: IPA core clock
- * @qmp: QMP handle for AOSS communication
- * @interconnect_count: Number of elements in interconnect[]
- * @interconnect: Interconnect array
- */
- struct ipa_power {
- struct device *dev;
- struct clk *core;
- struct qmp *qmp;
- u32 interconnect_count;
- struct icc_bulk_data interconnect[] __counted_by(interconnect_count);
- };
- /* Initialize interconnects required for IPA operation */
- static int ipa_interconnect_init(struct ipa_power *power,
- const struct ipa_interconnect_data *data)
- {
- struct icc_bulk_data *interconnect;
- int ret;
- u32 i;
- /* Initialize our interconnect data array for bulk operations */
- interconnect = &power->interconnect[0];
- for (i = 0; i < power->interconnect_count; i++) {
- /* interconnect->path is filled in by of_icc_bulk_get() */
- interconnect->name = data->name;
- interconnect->avg_bw = data->average_bandwidth;
- interconnect->peak_bw = data->peak_bandwidth;
- data++;
- interconnect++;
- }
- ret = of_icc_bulk_get(power->dev, power->interconnect_count,
- power->interconnect);
- if (ret)
- return ret;
- /* All interconnects are initially disabled */
- icc_bulk_disable(power->interconnect_count, power->interconnect);
- /* Set the bandwidth values to be used when enabled */
- ret = icc_bulk_set_bw(power->interconnect_count, power->interconnect);
- if (ret)
- icc_bulk_put(power->interconnect_count, power->interconnect);
- return ret;
- }
- /* Inverse of ipa_interconnect_init() */
- static void ipa_interconnect_exit(struct ipa_power *power)
- {
- icc_bulk_put(power->interconnect_count, power->interconnect);
- }
- /* Enable IPA power, enabling interconnects and the core clock */
- static int ipa_power_enable(struct ipa *ipa)
- {
- struct ipa_power *power = ipa->power;
- int ret;
- ret = icc_bulk_enable(power->interconnect_count, power->interconnect);
- if (ret)
- return ret;
- ret = clk_prepare_enable(power->core);
- if (ret) {
- dev_err(power->dev, "error %d enabling core clock\n", ret);
- icc_bulk_disable(power->interconnect_count,
- power->interconnect);
- }
- return ret;
- }
- /* Inverse of ipa_power_enable() */
- static void ipa_power_disable(struct ipa *ipa)
- {
- struct ipa_power *power = ipa->power;
- clk_disable_unprepare(power->core);
- icc_bulk_disable(power->interconnect_count, power->interconnect);
- }
- 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) {
- ipa_endpoint_suspend(ipa);
- gsi_suspend(&ipa->gsi);
- }
- ipa_power_disable(ipa);
- return 0;
- }
- 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);
- /* Increment the disable depth to ensure that the IRQ won't
- * be re-enabled until the matching _enable call in
- * ipa_resume(). We do this to ensure that the interrupt
- * handler won't run whilst PM runtime is disabled.
- *
- * Note that disabling the IRQ is NOT the same as disabling
- * irq wake. If wakeup is enabled for the IPA then the IRQ
- * will still cause the system to wake up, see irq_set_irq_wake().
- */
- ipa_interrupt_irq_disable(ipa);
- 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);
- /* Now that PM runtime is enabled again it's safe
- * to turn the IRQ back on and process any data
- * that was received during suspend.
- */
- ipa_interrupt_irq_enable(ipa);
- 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;
- }
- static int ipa_power_retention_init(struct ipa_power *power)
- {
- struct qmp *qmp = qmp_get(power->dev);
- if (IS_ERR(qmp)) {
- if (PTR_ERR(qmp) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- /* We assume any other error means it's not defined/needed */
- qmp = NULL;
- }
- power->qmp = qmp;
- return 0;
- }
- static void ipa_power_retention_exit(struct ipa_power *power)
- {
- qmp_put(power->qmp);
- power->qmp = NULL;
- }
- /* Control register retention on power collapse */
- void ipa_power_retention(struct ipa *ipa, bool enable)
- {
- static const char fmt[] = "{ class: bcm, res: ipa_pc, val: %c }";
- struct ipa_power *power = ipa->power;
- int ret;
- if (!power->qmp)
- return; /* Not needed on this platform */
- ret = qmp_send(power->qmp, fmt, enable ? '1' : '0');
- if (ret)
- dev_err(power->dev, "error %d sending QMP %sable request\n",
- ret, enable ? "en" : "dis");
- }
- /* 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;
- size_t size;
- int ret;
- clk = clk_get(dev, "core");
- if (IS_ERR(clk))
- return dev_err_cast_probe(dev, clk, "error getting core clock\n");
- 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;
- }
- size = struct_size(power, interconnect, data->interconnect_count);
- power = kzalloc(size, GFP_KERNEL);
- if (!power) {
- ret = -ENOMEM;
- goto err_clk_put;
- }
- power->dev = dev;
- power->core = clk;
- power->interconnect_count = data->interconnect_count;
- ret = ipa_interconnect_init(power, data->interconnect_data);
- if (ret)
- goto err_kfree;
- ret = ipa_power_retention_init(power);
- if (ret)
- goto err_interconnect_exit;
- pm_runtime_set_autosuspend_delay(dev, IPA_AUTOSUSPEND_DELAY);
- pm_runtime_use_autosuspend(dev);
- pm_runtime_enable(dev);
- return power;
- err_interconnect_exit:
- ipa_interconnect_exit(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_power_retention_exit(power);
- 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,
- };
|