/* * CPU frequency scaling for Broadcom SoCs with AVS firmware that * supports DVS or DVFS * * Copyright (c) 2016 Broadcom * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation version 2. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details.
*/
/* * "AVS" is the name of a firmware developed at Broadcom. It derives * its name from the technique called "Adaptive Voltage Scaling". * Adaptive voltage scaling was the original purpose of this firmware. * The AVS firmware still supports "AVS mode", where all it does is * adaptive voltage scaling. However, on some newer Broadcom SoCs, the * AVS Firmware, despite its unchanged name, also supports DFS mode and * DVFS mode. * * In the context of this document and the related driver, "AVS" by * itself always means the Broadcom firmware and never refers to the * technique called "Adaptive Voltage Scaling". * * The Broadcom STB AVS CPUfreq driver provides voltage and frequency * scaling on Broadcom SoCs using AVS firmware with support for DFS and * DVFS. The AVS firmware is running on its own co-processor. The * driver supports both uniprocessor (UP) and symmetric multiprocessor * (SMP) systems which share clock and voltage across all CPUs. * * Actual voltage and frequency scaling is done solely by the AVS * firmware. This driver does not change frequency or voltage itself. * It provides a standard CPUfreq interface to the rest of the kernel * and to userland. It interfaces with the AVS firmware to effect the * requested changes and to report back the current system status in a * way that is expected by existing tools.
*/
/* Max number of arguments AVS calls take */ #define AVS_MAX_CMD_ARGS 4 /* * This macro is used to generate AVS parameter register offsets. For * x >= AVS_MAX_CMD_ARGS, it returns 0 to protect against accidental memory * access outside of the parameter range. (Offset 0 is the first parameter.)
*/ #define AVS_PARAM_MULT(x) ((x) < AVS_MAX_CMD_ARGS ? (x) : 0)
ret = down_interruptible(&priv->sem); if (ret) return ret;
/* * Make sure no other command is currently running: cmd is 0 if AVS * co-processor is idle. Due to the guard above, we should almost never * have to wait here.
*/ for (i = 0, val = 1; val != 0 && i < AVS_LOOP_LIMIT; i++)
val = readl(base + AVS_MBOX_COMMAND);
/* Give the caller a chance to retry if AVS is busy. */ if (i == AVS_LOOP_LIMIT) {
ret = -EAGAIN; goto out;
}
/* Clear status before we begin. */
writel(AVS_STATUS_CLEAR, base + AVS_MBOX_STATUS);
/* Provide input parameters */ for (i = 0; i < num_in; i++)
writel(args[i], base + AVS_MBOX_PARAM(i));
/* Protect from spurious interrupts. */
reinit_completion(&priv->done);
/* Now issue the command & tell firmware to wake up to process it. */
writel(cmd, base + AVS_MBOX_COMMAND);
writel(AVS_CPU_L2_INT_MASK, priv->avs_intr_base + AVS_CPU_L2_SET0);
/* Wait for AVS co-processor to finish processing the command. */
time_left = wait_for_avs_command(priv, AVS_TIMEOUT);
/* * If the AVS status is not in the expected range, it means AVS didn't * complete our command in time, and we return an error. Also, if there * is no "time left", we timed out waiting for the interrupt.
*/
val = readl(base + AVS_MBOX_STATUS); if (time_left == 0 || val == 0 || val > AVS_STATUS_MAX) {
dev_err(priv->dev, "AVS command %#x didn't complete in time\n",
cmd);
dev_err(priv->dev, " Time left: %u ms, AVS status: %#x\n",
jiffies_to_msecs(time_left), val);
ret = -ETIMEDOUT; goto out;
}
/* Process returned values */ for (i = 0; i < num_out; i++)
args[i] = readl(base + AVS_MBOX_PARAM(i));
/* Clear status to tell AVS co-processor we are done. */
writel(AVS_STATUS_CLEAR, base + AVS_MBOX_STATUS);
/* Convert firmware errors to errno's as much as possible. */ switch (val) { case AVS_STATUS_INVALID:
ret = -EINVAL; break; case AVS_STATUS_NO_SUPP:
ret = -ENOTSUPP; break; case AVS_STATUS_NO_MAP:
ret = -ENOENT; break; case AVS_STATUS_MAP_SET:
ret = -EEXIST; break; case AVS_STATUS_FAILURE:
ret = -EIO; break;
}
/* * We determine which frequencies are supported by cycling through all P-states * and reading back what frequency we are running at for each P-state.
*/ staticstruct cpufreq_frequency_table *
brcm_avs_get_freq_table(struct device *dev, struct private_data *priv)
{ struct cpufreq_frequency_table *table; unsignedint pstate; int i, ret;
/* Remember P-state for later */
ret = brcm_avs_get_pstate(priv, &pstate); if (ret) return ERR_PTR(ret);
/* * We allocate space for the 5 different P-STATES AVS, * plus extra space for a terminating element.
*/
table = devm_kcalloc(dev, AVS_PSTATE_MAX + 1 + 1, sizeof(*table),
GFP_KERNEL); if (!table) return ERR_PTR(-ENOMEM);
for (i = AVS_PSTATE_P0; i <= AVS_PSTATE_MAX; i++) {
ret = brcm_avs_set_pstate(priv, i); if (ret) return ERR_PTR(ret);
table[i].frequency = brcm_avs_get_frequency(priv->base);
table[i].driver_data = i;
}
table[i].frequency = CPUFREQ_TABLE_END;
/* Restore P-state */
ret = brcm_avs_set_pstate(priv, pstate); if (ret) return ERR_PTR(ret);
return table;
}
/* * To ensure the right firmware is running we need to * - check the MAGIC matches what we expect * - brcm_avs_get_pmap() doesn't return -ENOTSUPP or -EINVAL * We need to set up our interrupt handling before calling brcm_avs_get_pmap()!
*/ staticbool brcm_avs_is_firmware_loaded(struct private_data *priv)
{
u32 magic; int rc;
ret = brcm_avs_get_pmap(priv, &priv->pmap); if (ret) return ret;
/* * We can't use the P-state returned by brcm_avs_get_pmap(), since * that's the initial P-state from when the P-map was downloaded to the * AVS co-processor, not necessarily the P-state we are running at now. * So, we get the current P-state explicitly.
*/
ret = brcm_avs_get_pstate(priv, &priv->pmap.state); if (ret) return ret;
/* This is best effort. Nothing to do if it fails. */
(void)__issue_avs_command(priv, AVS_CMD_S2_ENTER, 0, 0, NULL);
/* This is best effort. Nothing to do if it fails. */
(void)__issue_avs_command(priv, AVS_CMD_S2_EXIT, 0, 0, NULL);
ret = brcm_avs_set_pmap(priv, &priv->pmap); if (ret == -EEXIST) { struct platform_device *pdev = cpufreq_get_driver_data(); struct device *dev = &pdev->dev;
dev_warn(dev, "PMAP was already set\n");
ret = 0;
}
return ret;
}
/* * All initialization code that we only want to execute once goes here. Setup * code that can be re-tried on every core (if it failed before) can go into * brcm_avs_cpufreq_init().
*/ staticint brcm_avs_prepare_init(struct platform_device *pdev)
{ struct private_data *priv; struct device *dev; int ret;
dev = &pdev->dev;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.