Commit 981e6d96 authored by Linaro CI's avatar Linaro CI

Merge remote-tracking branch 'sdm845-qmp/tracking-qcomlt-sdm845-qmp' into integration-linux-qcomlt

# Conflicts:
#	arch/arm64/boot/dts/qcom/sdm845-mtp.dts
#	arch/arm64/boot/dts/qcom/sdm845.dtsi
parents 850977ad a78f8a09
Qualcomm Always-On Subsystem side channel binding
This binding describes the hardware component responsible for side channel
requests to the always-on subsystem (AOSS), used for certain power management
requests that is not handled by the standard RPMh interface. Each client in the
SoC has it's own block of message RAM and IRQ for communication with the AOSS.
The protocol used to communicate in the message RAM is known as Qualcomm
Messagin Protocol (QMP)
The AOSS side channel exposes control over a set of resources, used to control
a set of debug related clocks and to affect the low power state of resources
related to the secondary subsystems. These resources are exposed as a set of
power-domains.
- compatible:
Usage: required
Value type: <string>
Definition: must be "qcom,sdm845-aoss-qmp"
- reg:
Usage: required
Value type: <prop-encoded-array>
Definition: the base address and size of the message RAM for this
client's communication with the AOSS
- interrupts:
Usage: required
Value type: <prop-encoded-array>
Definition: should specify the AOSS message IRQ for this client
- mboxes:
Usage: required
Value type: <prop-encoded-array>
Definition: reference to the mailbox representing the outgoing doorbell
in APCS for this client, as described in mailbox/mailbox.txt
- #power-domain-cells:
Usage: optional
Value type: <u32>
Definition: must be 1
The provided power-domains are:
QDSS clock-domain (0), CDSP state (1), LPASS state (2),
modem state (3), SLPI state (4), SPSS state (5) and Venus
state (6).
= SUBNODES
The AOSS side channel also provides the controls for three cooling devices,
these are expressed as subnodes of the QMP node. The name of the node is used
to identify the resource and must therefor be "cx", "mx" or "ebi".
- #cooling-cells:
Usage: optional
Value type: <u32>
Definition: must be 2
= EXAMPLE
The following example represents the AOSS side-channel message RAM and the
mechanism exposing the power-domains, as found in SDM845.
aoss_qmp: [email protected] {
compatible = "qcom,sdm845-aoss-qmp";
reg = <0x0c300000 0x100000>;
interrupts = <GIC_SPI 389 IRQ_TYPE_EDGE_RISING>;
mboxes = <&apss_shared 0>;
#power-domain-cells = <1>;
cx_cdev: cx {
#cooling-cells = <2>;
};
mx_cdev: mx {
#cooling-cells = <2>;
};
};
......@@ -48,6 +48,10 @@
};
};
&adsp_pas {
status = "okay";
};
&apps_rsc {
pm8998-rpmh-regulators {
compatible = "qcom,pm8998-rpmh-regulators";
......@@ -344,6 +348,10 @@
};
};
&cdsp_pas {
status = "okay";
};
&dsi0 {
status = "okay";
vdda-supply = <&vdda_mipi_dsi0_1p2>;
......
This diff is collapsed.
......@@ -3,6 +3,24 @@
#
menu "Qualcomm SoC drivers"
config QCOM_AOSS_QMP
tristate "Qualcomm AOSS Messaging Driver"
depends on ARCH_QCOM || COMPILE_TEST
depends on MAILBOX
help
This driver provides the means for communicating with the
micro-controller in the AOSS, using QMP, to control certain resource
that are not exposed through RPMh.
config QCOM_AOSS_QMP_PD
tristate "Qualcomm AOSS Messaging Power Domain driver"
depends on QCOM_AOSS_QMP
select PM_GENERIC_DOMAINS
help
This driver provides the means of controlling the AOSS's handling of
low-power state for resources related to the remoteproc subsystems as
well as controlling the debug clocks.
config QCOM_COMMAND_DB
bool "Qualcomm Command DB"
depends on ARCH_QCOM || COMPILE_TEST
......
# SPDX-License-Identifier: GPL-2.0
CFLAGS_rpmh-rsc.o := -I$(src)
obj-$(CONFIG_QCOM_AOSS_QMP) += aoss-qmp.o
obj-$(CONFIG_QCOM_AOSS_QMP_PD) += aoss-qmp-pd.o
obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o
obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o
obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, Linaro Ltd
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/soc/qcom/aoss-qmp.h>
#include <dt-bindings/power/qcom-aoss-qmp.h>
/* Requests are expected to be 96 bytes long */
#define AOSS_QMP_PD_MSG_LEN 96
struct qmp_pd {
struct qmp *qmp;
struct generic_pm_domain pd;
};
#define to_qmp_pd_resource(res) container_of(res, struct qmp_pd, pd)
struct qmp_pd_resource {
const char *name;
int (*on)(struct generic_pm_domain *domain);
int (*off)(struct generic_pm_domain *domain);
};
static int qmp_pd_clock_toggle(struct qmp_pd *res, bool enable)
{
char buf[AOSS_QMP_PD_MSG_LEN];
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf), "{class: clock, res: %s, val: %d}",
res->pd.name, enable);
return qmp_send(res->qmp, buf, sizeof(buf));
}
static int qmp_pd_clock_on(struct generic_pm_domain *domain)
{
return qmp_pd_clock_toggle(to_qmp_pd_resource(domain), true);
}
static int qmp_pd_clock_off(struct generic_pm_domain *domain)
{
return qmp_pd_clock_toggle(to_qmp_pd_resource(domain), false);
}
static int qmp_pd_image_toggle(struct qmp_pd *res, bool enable)
{
char buf[AOSS_QMP_PD_MSG_LEN];
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf),
"{class: image, res: load_state, name: %s, val: %s}",
res->pd.name, enable ? "on" : "off");
return qmp_send(res->qmp, buf, sizeof(buf));
}
static int qmp_pd_image_on(struct generic_pm_domain *domain)
{
return qmp_pd_image_toggle(to_qmp_pd_resource(domain), true);
}
static int qmp_pd_image_off(struct generic_pm_domain *domain)
{
return qmp_pd_image_toggle(to_qmp_pd_resource(domain), false);
}
static const struct qmp_pd_resource sdm845_resources[] = {
[AOSS_QMP_QDSS_CLK] = { "qdss", qmp_pd_clock_on, qmp_pd_clock_off },
[AOSS_QMP_LS_CDSP] = { "cdsp", qmp_pd_image_on, qmp_pd_image_off },
[AOSS_QMP_LS_LPASS] = { "adsp", qmp_pd_image_on, qmp_pd_image_off },
[AOSS_QMP_LS_MODEM] = { "modem", qmp_pd_image_on, qmp_pd_image_off },
[AOSS_QMP_LS_SLPI] = { "slpi", qmp_pd_image_on, qmp_pd_image_off },
[AOSS_QMP_LS_SPSS] = { "spss", qmp_pd_image_on, qmp_pd_image_off },
[AOSS_QMP_LS_VENUS] = { "venus", qmp_pd_image_on, qmp_pd_image_off },
};
static int qmp_pd_probe(struct platform_device *pdev)
{
struct genpd_onecell_data *data;
struct device *parent = pdev->dev.parent;
struct qmp_pd *res;
struct qmp *qmp;
size_t num = ARRAY_SIZE(sdm845_resources);
int ret;
int i;
qmp = dev_get_drvdata(pdev->dev.parent);
if (!qmp)
return -EINVAL;
res = devm_kcalloc(&pdev->dev, num, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->domains = devm_kcalloc(&pdev->dev, num, sizeof(*data->domains),
GFP_KERNEL);
if (!data->domains)
return -ENOMEM;
for (i = 0; i < num; i++) {
res[i].qmp = qmp;
res[i].pd.name = sdm845_resources[i].name;
res[i].pd.power_on = sdm845_resources[i].on;
res[i].pd.power_off = sdm845_resources[i].off;
ret = pm_genpd_init(&res[i].pd, NULL, true);
if (ret < 0) {
dev_err(&pdev->dev, "failed to init genpd\n");
goto unroll_genpds;
}
data->domains[i] = &res[i].pd;
}
data->num_domains = i;
platform_set_drvdata(pdev, data);
return of_genpd_add_provider_onecell(parent->of_node, data);
unroll_genpds:
for (i--; i >= 0; i--)
pm_genpd_remove(data->domains[i]);
return ret;
}
static int qmp_pd_remove(struct platform_device *pdev)
{
struct device *parent = pdev->dev.parent;
struct genpd_onecell_data *data = platform_get_drvdata(pdev);
int i;
of_genpd_del_provider(parent->of_node);
for (i = 0; i < data->num_domains; i++)
pm_genpd_remove(data->domains[i]);
return 0;
}
static struct platform_driver qmp_pd_driver = {
.driver = {
.name = "aoss_qmp_pd",
},
.probe = qmp_pd_probe,
.remove = qmp_pd_remove,
};
module_platform_driver(qmp_pd_driver);
MODULE_ALIAS("platform:aoss_qmp_pd");
MODULE_DESCRIPTION("Qualcomm AOSS QMP load-state driver");
MODULE_LICENSE("GPL v2");
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, Linaro Ltd
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/soc/qcom/aoss-qmp.h>
#define QMP_DESC_MAGIC 0x0
#define QMP_DESC_VERSION 0x4
#define QMP_DESC_FEATURES 0x8
/* AOP-side offsets */
#define QMP_DESC_UCORE_LINK_STATE 0xc
#define QMP_DESC_UCORE_LINK_STATE_ACK 0x10
#define QMP_DESC_UCORE_CH_STATE 0x14
#define QMP_DESC_UCORE_CH_STATE_ACK 0x18
#define QMP_DESC_UCORE_MBOX_SIZE 0x1c
#define QMP_DESC_UCORE_MBOX_OFFSET 0x20
/* Linux-side offsets */
#define QMP_DESC_MCORE_LINK_STATE 0x24
#define QMP_DESC_MCORE_LINK_STATE_ACK 0x28
#define QMP_DESC_MCORE_CH_STATE 0x2c
#define QMP_DESC_MCORE_CH_STATE_ACK 0x30
#define QMP_DESC_MCORE_MBOX_SIZE 0x34
#define QMP_DESC_MCORE_MBOX_OFFSET 0x38
#define QMP_STATE_UP 0x0000ffff
#define QMP_STATE_DOWN 0xffff0000
#define QMP_MAGIC 0x4d41494c
#define QMP_VERSION 1
/**
* struct qmp - driver state for QMP implementation
* @msgram: iomem referencing the message RAM used for communication
* @dev: reference to QMP device
* @mbox_client: mailbox client used to ring the doorbell on transmit
* @mbox_chan: mailbox channel used to ring the doorbell on transmit
* @offset: offset within @msgram where messages should be written
* @size: maximum size of the messages to be transmitted
* @event: wait_queue for synchronization with the IRQ
* @tx_lock: provides syncrhonization between multiple callers of qmp_send()
* @pd_pdev: platform device for the power-domain child device
*/
struct qmp {
void __iomem *msgram;
struct device *dev;
struct mbox_client mbox_client;
struct mbox_chan *mbox_chan;
size_t offset;
size_t size;
wait_queue_head_t event;
struct mutex tx_lock;
struct platform_device *pd_pdev;
};
static void qmp_kick(struct qmp *qmp)
{
mbox_send_message(qmp->mbox_chan, NULL);
mbox_client_txdone(qmp->mbox_chan, 0);
}
static bool qmp_magic_valid(struct qmp *qmp)
{
return readl(qmp->msgram + QMP_DESC_MAGIC) == QMP_MAGIC;
}
static bool qmp_link_acked(struct qmp *qmp)
{
return readl(qmp->msgram + QMP_DESC_MCORE_LINK_STATE_ACK) == QMP_STATE_UP;
}
static bool qmp_mcore_channel_acked(struct qmp *qmp)
{
return readl(qmp->msgram + QMP_DESC_MCORE_CH_STATE_ACK) == QMP_STATE_UP;
}
static bool qmp_ucore_channel_up(struct qmp *qmp)
{
return readl(qmp->msgram + QMP_DESC_UCORE_CH_STATE) == QMP_STATE_UP;
}
static int qmp_open(struct qmp *qmp)
{
int ret;
u32 val;
if (!qmp_magic_valid(qmp)) {
dev_err(qmp->dev, "QMP magic doesn't match\n");
return -ETIMEDOUT;
}
val = readl(qmp->msgram + QMP_DESC_VERSION);
if (val != QMP_VERSION) {
dev_err(qmp->dev, "unsupported QMP version %d\n", val);
return -EINVAL;
}
qmp->offset = readl(qmp->msgram + QMP_DESC_MCORE_MBOX_OFFSET);
qmp->size = readl(qmp->msgram + QMP_DESC_MCORE_MBOX_SIZE);
if (!qmp->size) {
dev_err(qmp->dev, "invalid mailbox size\n");
return -EINVAL;
}
/* Ack remote core's link state */
val = readl(qmp->msgram + QMP_DESC_UCORE_LINK_STATE);
writel(val, qmp->msgram + QMP_DESC_UCORE_LINK_STATE_ACK);
/* Set local core's link state to up */
writel(QMP_STATE_UP, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
qmp_kick(qmp);
ret = wait_event_timeout(qmp->event, qmp_link_acked(qmp), HZ);
if (!ret) {
dev_err(qmp->dev, "ucore didn't ack link\n");
goto timeout_close_link;
}
writel(QMP_STATE_UP, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
qmp_kick(qmp);
ret = wait_event_timeout(qmp->event, qmp_ucore_channel_up(qmp), HZ);
if (!ret) {
dev_err(qmp->dev, "ucore didn't open channel\n");
goto timeout_close_channel;
}
/* Ack remote core's channel state */
writel(QMP_STATE_UP, qmp->msgram + QMP_DESC_UCORE_CH_STATE_ACK);
qmp_kick(qmp);
ret = wait_event_timeout(qmp->event, qmp_mcore_channel_acked(qmp), HZ);
if (!ret) {
dev_err(qmp->dev, "ucore didn't ack channel\n");
goto timeout_close_channel;
}
return 0;
timeout_close_channel:
writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
timeout_close_link:
writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
qmp_kick(qmp);
return -ETIMEDOUT;
}
static void qmp_close(struct qmp *qmp)
{
writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_CH_STATE);
writel(QMP_STATE_DOWN, qmp->msgram + QMP_DESC_MCORE_LINK_STATE);
qmp_kick(qmp);
}
static irqreturn_t qmp_intr(int irq, void *data)
{
struct qmp *qmp = data;
wake_up_interruptible_all(&qmp->event);
return IRQ_HANDLED;
}
static bool qmp_message_empty(struct qmp *qmp)
{
return readl(qmp->msgram + qmp->offset) == 0;
}
/**
* qmp_send() - send a message to the AOSS
* @qmp: qmp context
* @data: message to be sent
* @len: length of the message
*
* Transmit @data to AOSS and wait for the AOSS to acknowledge the message.
* @len must be a multiple of 4 and not longer than the mailbox size. Access is
* synchronized by this implementation.
*
* Return: 0 on success, negative errno on failure
*/
int qmp_send(struct qmp *qmp, const void *data, size_t len)
{
int ret;
if (WARN_ON(len + sizeof(u32) > qmp->size))
return -EINVAL;
if (WARN_ON(len % sizeof(u32)))
return -EINVAL;
mutex_lock(&qmp->tx_lock);
/* The message RAM only implements 32-bit accesses */
__iowrite32_copy(qmp->msgram + qmp->offset + sizeof(u32),
data, len / sizeof(u32));
writel(len, qmp->msgram + qmp->offset);
qmp_kick(qmp);
ret = wait_event_interruptible_timeout(qmp->event,
qmp_message_empty(qmp), HZ);
if (!ret) {
dev_err(qmp->dev, "ucore did not ack channel\n");
ret = -ETIMEDOUT;
/* Clear message from buffer */
writel(0, qmp->msgram + qmp->offset);
} else {
ret = 0;
}
mutex_unlock(&qmp->tx_lock);
return ret;
}
EXPORT_SYMBOL(qmp_send);
static int qmp_probe(struct platform_device *pdev)
{
struct resource *res;
struct qmp *qmp;
int irq;
int ret;
qmp = devm_kzalloc(&pdev->dev, sizeof(*qmp), GFP_KERNEL);
if (!qmp)
return -ENOMEM;
qmp->dev = &pdev->dev;
init_waitqueue_head(&qmp->event);
mutex_init(&qmp->tx_lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
qmp->msgram = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(qmp->msgram))
return PTR_ERR(qmp->msgram);
qmp->mbox_client.dev = &pdev->dev;
qmp->mbox_client.knows_txdone = true;
qmp->mbox_chan = mbox_request_channel(&qmp->mbox_client, 0);
if (IS_ERR(qmp->mbox_chan)) {
dev_err(&pdev->dev, "failed to acquire ipc mailbox\n");
return PTR_ERR(qmp->mbox_chan);
}
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, qmp_intr, IRQF_ONESHOT,
"aoss-qmp", qmp);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request interrupt\n");
goto err_close_qmp;
}
ret = qmp_open(qmp);
if (ret < 0)
goto err_free_mbox;
platform_set_drvdata(pdev, qmp);
if (of_property_read_bool(pdev->dev.of_node, "#power-domain-cells")) {
qmp->pd_pdev = platform_device_register_data(&pdev->dev,
"aoss_qmp_pd",
PLATFORM_DEVID_NONE,
NULL, 0);
if (IS_ERR(qmp->pd_pdev)) {
dev_err(&pdev->dev, "failed to register AOSS PD\n");
ret = PTR_ERR(qmp->pd_pdev);
goto err_close_qmp;
}
}
return 0;
err_close_qmp:
qmp_close(qmp);
err_free_mbox:
mbox_free_channel(qmp->mbox_chan);
return ret;
}
static int qmp_remove(struct platform_device *pdev)
{
struct qmp *qmp = platform_get_drvdata(pdev);
platform_device_unregister(qmp->pd_pdev);
qmp_close(qmp);
mbox_free_channel(qmp->mbox_chan);
return 0;
}
static const struct of_device_id qmp_dt_match[] = {
{ .compatible = "qcom,sdm845-aoss-qmp", },
{}
};
MODULE_DEVICE_TABLE(of, qmp_dt_match);
static struct platform_driver qmp_driver = {
.driver = {
.name = "aoss_qmp",
.of_match_table = qmp_dt_match,
},
.probe = qmp_probe,
.remove = qmp_remove,
};
module_platform_driver(qmp_driver);
MODULE_DESCRIPTION("Qualcomm AOSS QMP driver");
MODULE_LICENSE("GPL v2");
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018, Linaro Ltd. */
#ifndef __DT_BINDINGS_POWER_QCOM_AOSS_QMP_H
#define __DT_BINDINGS_POWER_QCOM_AOSS_QMP_H
#define AOSS_QMP_QDSS_CLK 0
#define AOSS_QMP_LS_CDSP 1
#define AOSS_QMP_LS_LPASS 2
#define AOSS_QMP_LS_MODEM 3
#define AOSS_QMP_LS_SLPI 4
#define AOSS_QMP_LS_SPSS 5
#define AOSS_QMP_LS_VENUS 6
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2018, Linaro Ltd
*/
#ifndef __AOP_QMP_H__
#define __AOP_QMP_H__
#include <linux/types.h>
struct qmp;
int qmp_send(struct qmp *qmp, const void *data, size_t len);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment