brintos

brintos / linux-shallow public Read only

0
0
Text · 7.4 KiB · c572da9 Raw
309 lines · c
1// SPDX-License-Identifier: GPL-2.02 3/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.4 * Copyright (C) 2018-2024 Linaro Ltd.5 */6 7#include <linux/clk.h>8#include <linux/device.h>9#include <linux/interconnect.h>10#include <linux/pm.h>11#include <linux/pm_runtime.h>12 13#include "linux/soc/qcom/qcom_aoss.h"14 15#include "ipa.h"16#include "ipa_data.h"17#include "ipa_endpoint.h"18#include "ipa_interrupt.h"19#include "ipa_modem.h"20#include "ipa_power.h"21 22/**23 * DOC: IPA Power Management24 *25 * The IPA hardware is enabled when the IPA core clock and all the26 * interconnects (buses) it depends on are enabled.  Runtime power27 * management is used to determine whether the core clock and28 * interconnects are enabled, and if not in use to be suspended29 * automatically.30 *31 * The core clock currently runs at a fixed clock rate when enabled,32 * an all interconnects use a fixed average and peak bandwidth.33 */34 35#define IPA_AUTOSUSPEND_DELAY	500	/* milliseconds */36 37/**38 * struct ipa_power - IPA power management information39 * @dev:		IPA device pointer40 * @core:		IPA core clock41 * @qmp:		QMP handle for AOSS communication42 * @interconnect_count:	Number of elements in interconnect[]43 * @interconnect:	Interconnect array44 */45struct ipa_power {46	struct device *dev;47	struct clk *core;48	struct qmp *qmp;49	u32 interconnect_count;50	struct icc_bulk_data interconnect[] __counted_by(interconnect_count);51};52 53/* Initialize interconnects required for IPA operation */54static int ipa_interconnect_init(struct ipa_power *power,55				 const struct ipa_interconnect_data *data)56{57	struct icc_bulk_data *interconnect;58	int ret;59	u32 i;60 61	/* Initialize our interconnect data array for bulk operations */62	interconnect = &power->interconnect[0];63	for (i = 0; i < power->interconnect_count; i++) {64		/* interconnect->path is filled in by of_icc_bulk_get() */65		interconnect->name = data->name;66		interconnect->avg_bw = data->average_bandwidth;67		interconnect->peak_bw = data->peak_bandwidth;68		data++;69		interconnect++;70	}71 72	ret = of_icc_bulk_get(power->dev, power->interconnect_count,73			      power->interconnect);74	if (ret)75		return ret;76 77	/* All interconnects are initially disabled */78	icc_bulk_disable(power->interconnect_count, power->interconnect);79 80	/* Set the bandwidth values to be used when enabled */81	ret = icc_bulk_set_bw(power->interconnect_count, power->interconnect);82	if (ret)83		icc_bulk_put(power->interconnect_count, power->interconnect);84 85	return ret;86}87 88/* Inverse of ipa_interconnect_init() */89static void ipa_interconnect_exit(struct ipa_power *power)90{91	icc_bulk_put(power->interconnect_count, power->interconnect);92}93 94/* Enable IPA power, enabling interconnects and the core clock */95static int ipa_power_enable(struct ipa *ipa)96{97	struct ipa_power *power = ipa->power;98	int ret;99 100	ret = icc_bulk_enable(power->interconnect_count, power->interconnect);101	if (ret)102		return ret;103 104	ret = clk_prepare_enable(power->core);105	if (ret) {106		dev_err(power->dev, "error %d enabling core clock\n", ret);107		icc_bulk_disable(power->interconnect_count,108				 power->interconnect);109	}110 111	return ret;112}113 114/* Inverse of ipa_power_enable() */115static void ipa_power_disable(struct ipa *ipa)116{117	struct ipa_power *power = ipa->power;118 119	clk_disable_unprepare(power->core);120 121	icc_bulk_disable(power->interconnect_count, power->interconnect);122}123 124static int ipa_runtime_suspend(struct device *dev)125{126	struct ipa *ipa = dev_get_drvdata(dev);127 128	/* Endpoints aren't usable until setup is complete */129	if (ipa->setup_complete) {130		ipa_endpoint_suspend(ipa);131		gsi_suspend(&ipa->gsi);132	}133 134	ipa_power_disable(ipa);135 136	return 0;137}138 139static int ipa_runtime_resume(struct device *dev)140{141	struct ipa *ipa = dev_get_drvdata(dev);142	int ret;143 144	ret = ipa_power_enable(ipa);145	if (WARN_ON(ret < 0))146		return ret;147 148	/* Endpoints aren't usable until setup is complete */149	if (ipa->setup_complete) {150		gsi_resume(&ipa->gsi);151		ipa_endpoint_resume(ipa);152	}153 154	return 0;155}156 157static int ipa_suspend(struct device *dev)158{159	struct ipa *ipa = dev_get_drvdata(dev);160 161	/* Increment the disable depth to ensure that the IRQ won't162	 * be re-enabled until the matching _enable call in163	 * ipa_resume(). We do this to ensure that the interrupt164	 * handler won't run whilst PM runtime is disabled.165	 *166	 * Note that disabling the IRQ is NOT the same as disabling167	 * irq wake. If wakeup is enabled for the IPA then the IRQ168	 * will still cause the system to wake up, see irq_set_irq_wake().169	 */170	ipa_interrupt_irq_disable(ipa);171 172	return pm_runtime_force_suspend(dev);173}174 175static int ipa_resume(struct device *dev)176{177	struct ipa *ipa = dev_get_drvdata(dev);178	int ret;179 180	ret = pm_runtime_force_resume(dev);181 182	/* Now that PM runtime is enabled again it's safe183	 * to turn the IRQ back on and process any data184	 * that was received during suspend.185	 */186	ipa_interrupt_irq_enable(ipa);187 188	return ret;189}190 191/* Return the current IPA core clock rate */192u32 ipa_core_clock_rate(struct ipa *ipa)193{194	return ipa->power ? (u32)clk_get_rate(ipa->power->core) : 0;195}196 197static int ipa_power_retention_init(struct ipa_power *power)198{199	struct qmp *qmp = qmp_get(power->dev);200 201	if (IS_ERR(qmp)) {202		if (PTR_ERR(qmp) == -EPROBE_DEFER)203			return -EPROBE_DEFER;204 205		/* We assume any other error means it's not defined/needed */206		qmp = NULL;207	}208	power->qmp = qmp;209 210	return 0;211}212 213static void ipa_power_retention_exit(struct ipa_power *power)214{215	qmp_put(power->qmp);216	power->qmp = NULL;217}218 219/* Control register retention on power collapse */220void ipa_power_retention(struct ipa *ipa, bool enable)221{222	static const char fmt[] = "{ class: bcm, res: ipa_pc, val: %c }";223	struct ipa_power *power = ipa->power;224	int ret;225 226	if (!power->qmp)227		return;		/* Not needed on this platform */228 229	ret = qmp_send(power->qmp, fmt, enable ? '1' : '0');230	if (ret)231		dev_err(power->dev, "error %d sending QMP %sable request\n",232			ret, enable ? "en" : "dis");233}234 235/* Initialize IPA power management */236struct ipa_power *237ipa_power_init(struct device *dev, const struct ipa_power_data *data)238{239	struct ipa_power *power;240	struct clk *clk;241	size_t size;242	int ret;243 244	clk = clk_get(dev, "core");245	if (IS_ERR(clk))246		return dev_err_cast_probe(dev, clk, "error getting core clock\n");247 248	ret = clk_set_rate(clk, data->core_clock_rate);249	if (ret) {250		dev_err(dev, "error %d setting core clock rate to %u\n",251			ret, data->core_clock_rate);252		goto err_clk_put;253	}254 255	size = struct_size(power, interconnect, data->interconnect_count);256	power = kzalloc(size, GFP_KERNEL);257	if (!power) {258		ret = -ENOMEM;259		goto err_clk_put;260	}261	power->dev = dev;262	power->core = clk;263	power->interconnect_count = data->interconnect_count;264 265	ret = ipa_interconnect_init(power, data->interconnect_data);266	if (ret)267		goto err_kfree;268 269	ret = ipa_power_retention_init(power);270	if (ret)271		goto err_interconnect_exit;272 273	pm_runtime_set_autosuspend_delay(dev, IPA_AUTOSUSPEND_DELAY);274	pm_runtime_use_autosuspend(dev);275	pm_runtime_enable(dev);276 277	return power;278 279err_interconnect_exit:280	ipa_interconnect_exit(power);281err_kfree:282	kfree(power);283err_clk_put:284	clk_put(clk);285 286	return ERR_PTR(ret);287}288 289/* Inverse of ipa_power_init() */290void ipa_power_exit(struct ipa_power *power)291{292	struct device *dev = power->dev;293	struct clk *clk = power->core;294 295	pm_runtime_disable(dev);296	pm_runtime_dont_use_autosuspend(dev);297	ipa_power_retention_exit(power);298	ipa_interconnect_exit(power);299	kfree(power);300	clk_put(clk);301}302 303const struct dev_pm_ops ipa_pm_ops = {304	.suspend		= ipa_suspend,305	.resume			= ipa_resume,306	.runtime_suspend	= ipa_runtime_suspend,307	.runtime_resume		= ipa_runtime_resume,308};309