1
0
Fork 0

Qualcomm ARM Based Driver Updates for v5.3

* Add ACPI support to Qualcomm GENI SE
 * Update Qualcomm Maintainers entry to remove David Brown as maintainer and
   fixup typos and incorrect DT file entry
 * Fixup APR domain id usage and making callbacks in non-atomic context
 * Add AOSS QMP driver and bindings
 * Add power domains for MSM8998 and QCS404 in QCOM RPMPD
 * Add corner macros, max state support, and fixups for setting performance state
   for Qcom RPMPD
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJdCeKEAAoJEFKiBbHx2RXVwvsP/3SBUTeyDP7HsD1+D9f8ojhC
 8TDQIpQw9jB+txczdnnoF5vewALpwc3E+intuUZd3oOPB6Nw/g6VU73HDGSDOBHc
 nbAf5UjpJgyo7XI+tJk9/m27Ee7y+MIzrk2qtDoboOv0/PZzfrAoFpm75mFJsylw
 luf3TMCTDd23aj6HxVe6dzz5F0gHVxF8hldlDkgiQZXGK0h/yInHEYgRe8vBdQGD
 wRkU8jKBU49n3OOY7qqD9PKKNSREC6nYxWIK/EJdjaMSNlwmzNJYUXANgtJj4/6G
 WmNBA84EplqDJIz6GJ6u5oTYQYISxNlvk0w0USmtXFTktv4wP1IsXb9swZGsYD2B
 lD825pM72tO3YqS14HHZ/GvxNsZvn5THPlUrp5Z5s5o+l4HL88FxHOfd6vk/H5mF
 dP/MO3xSIRD65FHZMOoSevAJkXtxZhAYNcaTBWWCPpJwgb6iPOY4q17R5ODATJgA
 5qJmebzWEn0AoseVEx6PTiejInf6yoDeC5iwcYEjIXqESZrLTvs0+kuZ/nbsD5v4
 1yUWGpw1fhVctYmesJk7CfQ2tFLbTtSH+1XTxHnqltriy1I01meTIA5azSaAumzx
 YGzVhmsAp0RxmNPjB41L8B5A3V1LfqXcQwauT5pCzGif9uQKxpjUoS6gSwvAa8Da
 QAuwPgQEH5ISDat755pO
 =JunO
 -----END PGP SIGNATURE-----

Merge tag 'qcom-drivers-for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux into arm/drivers

Qualcomm ARM Based Driver Updates for v5.3

* Add ACPI support to Qualcomm GENI SE
* Update Qualcomm Maintainers entry to remove David Brown as maintainer and
  fixup typos and incorrect DT file entry
* Fixup APR domain id usage and making callbacks in non-atomic context
* Add AOSS QMP driver and bindings
* Add power domains for MSM8998 and QCS404 in QCOM RPMPD
* Add corner macros, max state support, and fixups for setting performance state
  for Qcom RPMPD

* tag 'qcom-drivers-for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/qcom/linux:
  soc: qcom: geni: Add support for ACPI
  MAINTAINERS: Remove myself as qcom maintainer
  soc: qcom: apr: Don't use reg for domain id
  soc: qcom: fix QCOM_AOSS_QMP dependency and build errors
  soc: qcom: Add AOSS QMP driver
  dt-bindings: soc: qcom: Add AOSS QMP binding
  qcom: apr: Make apr callbacks in non-atomic context
  soc: qcom: rpmpd: Add MSM8998 power-domains
  dt-bindings: power: Add rpm power domain bindings for msm8998
  soc: qcom: rpmpd: Add QCS404 power-domains
  dt-bindings: power: Add rpm power domain bindings for qcs404
  soc: qcom: rpmpd: Modify corner defining macros
  soc: qcom: rpmpd: Add support to set rpmpd state to max
  soc: qcom: rpmpd: fixup rpmpd set performance state
  MAINTAINER: Fix Qualcomm ETHQOS ethernet DT file
  MAINTAINERS: fix typo in file name

Signed-off-by: Olof Johansson <olof@lixom.net>
alistair/sunxi64-5.4-dsi
Olof Johansson 2019-06-25 05:39:44 -07:00
commit c616ea191d
12 changed files with 824 additions and 42 deletions

View File

@ -6,6 +6,8 @@ which then translates it into a corresponding voltage on a rail
Required Properties:
- compatible: Should be one of the following
* qcom,msm8996-rpmpd: RPM Power domain for the msm8996 family of SoC
* qcom,msm8998-rpmpd: RPM Power domain for the msm8998 family of SoC
* qcom,qcs404-rpmpd: RPM Power domain for the qcs404 family of SoC
* qcom,sdm845-rpmhpd: RPMh Power domain for the sdm845 family of SoC
- #power-domain-cells: number of cells in Power domain specifier
must be 1.

View File

@ -0,0 +1,81 @@
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
Messaging 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
- #clock-cells:
Usage: optional
Value type: <u32>
Definition: must be 0
The single clock represents the QDSS clock.
- #power-domain-cells:
Usage: optional
Value type: <u32>
Definition: must be 1
The provided power-domains are:
CDSP state (0), LPASS state (1), modem state (2), SLPI
state (3), SPSS state (4) and Venus state (5).
= 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: qmp@c300000 {
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>;
};
};

View File

@ -9,7 +9,7 @@ used for audio/voice services on the QDSP.
Value type: <stringlist>
Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2"
- reg
- qcom,apr-domain
Usage: required
Value type: <u32>
Definition: Destination processor ID.
@ -49,9 +49,9 @@ by the individual bindings for the specific service
The following example represents a QDSP based sound card on a MSM8996 device
which uses apr as communication between Apps and QDSP.
apr@4 {
apr {
compatible = "qcom,apr-v2";
reg = <APR_DOMAIN_ADSP>;
qcom,apr-domain = <APR_DOMAIN_ADSP>;
q6core@3 {
compatible = "qcom,q6core";

View File

@ -2050,7 +2050,6 @@ S: Maintained
ARM/QUALCOMM SUPPORT
M: Andy Gross <agross@kernel.org>
M: David Brown <david.brown@linaro.org>
L: linux-arm-msm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/soc/qcom/
@ -2072,7 +2071,7 @@ F: drivers/i2c/busses/i2c-qup.c
F: drivers/i2c/busses/i2c-qcom-geni.c
F: drivers/mfd/ssbi.c
F: drivers/mmc/host/mmci_qcom*
F: drivers/mmc/host/sdhci_msm.c
F: drivers/mmc/host/sdhci-msm.c
F: drivers/pci/controller/dwc/pcie-qcom.c
F: drivers/phy/qualcomm/
F: drivers/power/*/msm*
@ -13056,7 +13055,7 @@ M: Niklas Cassel <niklas.cassel@linaro.org>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c
F: Documentation/devicetree/bindings/net/qcom,dwmac.txt
F: Documentation/devicetree/bindings/net/qcom,ethqos.txt
QUALCOMM GENERIC INTERFACE I2C DRIVER
M: Alok Chauhan <alokc@codeaurora.org>

View File

@ -4,6 +4,18 @@
#
menu "Qualcomm SoC drivers"
config QCOM_AOSS_QMP
tristate "Qualcomm AOSS Driver"
depends on ARCH_QCOM || COMPILE_TEST
depends on MAILBOX
depends on COMMON_CLK && PM
select PM_GENERIC_DOMAINS
help
This driver provides the means of communicating with and controlling
the low-power state for resources related to the remoteproc
subsystems as well as controlling the debug clocks exposed by the Always On
Subsystem (AOSS) using Qualcomm Messaging Protocol (QMP).
config QCOM_COMMAND_DB
bool "Qualcomm Command DB"
depends on ARCH_QCOM || COMPILE_TEST

View File

@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS_rpmh-rsc.o := -I$(src)
obj-$(CONFIG_QCOM_AOSS_QMP) += qcom_aoss.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

View File

@ -8,6 +8,7 @@
#include <linux/spinlock.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/of_device.h>
#include <linux/soc/qcom/apr.h>
#include <linux/rpmsg.h>
@ -17,8 +18,18 @@ struct apr {
struct rpmsg_endpoint *ch;
struct device *dev;
spinlock_t svcs_lock;
spinlock_t rx_lock;
struct idr svcs_idr;
int dest_domain_id;
struct workqueue_struct *rxwq;
struct work_struct rx_work;
struct list_head rx_list;
};
struct apr_rx_buf {
struct list_head node;
int len;
uint8_t buf[];
};
/**
@ -62,11 +73,7 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
int len, void *priv, u32 addr)
{
struct apr *apr = dev_get_drvdata(&rpdev->dev);
uint16_t hdr_size, msg_type, ver, svc_id;
struct apr_device *svc = NULL;
struct apr_driver *adrv = NULL;
struct apr_resp_pkt resp;
struct apr_hdr *hdr;
struct apr_rx_buf *abuf;
unsigned long flags;
if (len <= APR_HDR_SIZE) {
@ -75,6 +82,34 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
return -EINVAL;
}
abuf = kzalloc(sizeof(*abuf) + len, GFP_ATOMIC);
if (!abuf)
return -ENOMEM;
abuf->len = len;
memcpy(abuf->buf, buf, len);
spin_lock_irqsave(&apr->rx_lock, flags);
list_add_tail(&abuf->node, &apr->rx_list);
spin_unlock_irqrestore(&apr->rx_lock, flags);
queue_work(apr->rxwq, &apr->rx_work);
return 0;
}
static int apr_do_rx_callback(struct apr *apr, struct apr_rx_buf *abuf)
{
uint16_t hdr_size, msg_type, ver, svc_id;
struct apr_device *svc = NULL;
struct apr_driver *adrv = NULL;
struct apr_resp_pkt resp;
struct apr_hdr *hdr;
unsigned long flags;
void *buf = abuf->buf;
int len = abuf->len;
hdr = buf;
ver = APR_HDR_FIELD_VER(hdr->hdr_field);
if (ver > APR_PKT_VER + 1)
@ -132,6 +167,23 @@ static int apr_callback(struct rpmsg_device *rpdev, void *buf,
return 0;
}
static void apr_rxwq(struct work_struct *work)
{
struct apr *apr = container_of(work, struct apr, rx_work);
struct apr_rx_buf *abuf, *b;
unsigned long flags;
if (!list_empty(&apr->rx_list)) {
list_for_each_entry_safe(abuf, b, &apr->rx_list, node) {
apr_do_rx_callback(apr, abuf);
spin_lock_irqsave(&apr->rx_lock, flags);
list_del(&abuf->node);
spin_unlock_irqrestore(&apr->rx_lock, flags);
kfree(abuf);
}
}
}
static int apr_device_match(struct device *dev, struct device_driver *drv)
{
struct apr_device *adev = to_apr_device(dev);
@ -276,7 +328,7 @@ static int apr_probe(struct rpmsg_device *rpdev)
if (!apr)
return -ENOMEM;
ret = of_property_read_u32(dev->of_node, "reg", &apr->dest_domain_id);
ret = of_property_read_u32(dev->of_node, "qcom,apr-domain", &apr->dest_domain_id);
if (ret) {
dev_err(dev, "APR Domain ID not specified in DT\n");
return ret;
@ -285,6 +337,14 @@ static int apr_probe(struct rpmsg_device *rpdev)
dev_set_drvdata(dev, apr);
apr->ch = rpdev->ept;
apr->dev = dev;
apr->rxwq = create_singlethread_workqueue("qcom_apr_rx");
if (!apr->rxwq) {
dev_err(apr->dev, "Failed to start Rx WQ\n");
return -ENOMEM;
}
INIT_WORK(&apr->rx_work, apr_rxwq);
INIT_LIST_HEAD(&apr->rx_list);
spin_lock_init(&apr->rx_lock);
spin_lock_init(&apr->svcs_lock);
idr_init(&apr->svcs_idr);
of_register_apr_devices(dev);
@ -303,7 +363,11 @@ static int apr_remove_device(struct device *dev, void *null)
static void apr_remove(struct rpmsg_device *rpdev)
{
struct apr *apr = dev_get_drvdata(&rpdev->dev);
device_for_each_child(&rpdev->dev, NULL, apr_remove_device);
flush_workqueue(apr->rxwq);
destroy_workqueue(apr->rxwq);
}
/*

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
#include <linux/acpi.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
@ -450,6 +451,9 @@ int geni_se_resources_off(struct geni_se *se)
{
int ret;
if (has_acpi_companion(se->dev))
return 0;
ret = pinctrl_pm_select_sleep_state(se->dev);
if (ret)
return ret;
@ -487,6 +491,9 @@ int geni_se_resources_on(struct geni_se *se)
{
int ret;
if (has_acpi_companion(se->dev))
return 0;
ret = geni_se_clks_on(se);
if (ret)
return ret;
@ -724,12 +731,14 @@ static int geni_se_probe(struct platform_device *pdev)
if (IS_ERR(wrapper->base))
return PTR_ERR(wrapper->base);
wrapper->ahb_clks[0].id = "m-ahb";
wrapper->ahb_clks[1].id = "s-ahb";
ret = devm_clk_bulk_get(dev, NUM_AHB_CLKS, wrapper->ahb_clks);
if (ret) {
dev_err(dev, "Err getting AHB clks %d\n", ret);
return ret;
if (!has_acpi_companion(&pdev->dev)) {
wrapper->ahb_clks[0].id = "m-ahb";
wrapper->ahb_clks[1].id = "s-ahb";
ret = devm_clk_bulk_get(dev, NUM_AHB_CLKS, wrapper->ahb_clks);
if (ret) {
dev_err(dev, "Err getting AHB clks %d\n", ret);
return ret;
}
}
dev_set_drvdata(dev, wrapper);

View File

@ -0,0 +1,480 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019, Linaro Ltd
*/
#include <dt-bindings/power/qcom-aoss-qmp.h>
#include <linux/clk-provider.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.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 GENMASK(15, 0)
#define QMP_STATE_DOWN GENMASK(31, 16)
#define QMP_MAGIC 0x4d41494c /* mail */
#define QMP_VERSION 1
/* 64 bytes is enough to store the requests and provides padding to 4 bytes */
#define QMP_MSG_LEN 64
/**
* 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 synchronization between multiple callers of qmp_send()
* @qdss_clk: QDSS clock hw struct
* @pd_data: genpd data
*/
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 clk_hw qdss_clk;
struct genpd_onecell_data pd_data;
};
struct qmp_pd {
struct qmp *qmp;
struct generic_pm_domain pd;
};
#define to_qmp_pd_resource(res) container_of(res, struct qmp_pd, pd)
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 -EINVAL;
}
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
*/
static int qmp_send(struct qmp *qmp, const void *data, size_t len)
{
long time_left;
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);
time_left = wait_event_interruptible_timeout(qmp->event,
qmp_message_empty(qmp), HZ);
if (!time_left) {
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;
}
static int qmp_qdss_clk_prepare(struct clk_hw *hw)
{
static const char buf[QMP_MSG_LEN] = "{class: clock, res: qdss, val: 1}";
struct qmp *qmp = container_of(hw, struct qmp, qdss_clk);
return qmp_send(qmp, buf, sizeof(buf));
}
static void qmp_qdss_clk_unprepare(struct clk_hw *hw)
{
static const char buf[QMP_MSG_LEN] = "{class: clock, res: qdss, val: 0}";
struct qmp *qmp = container_of(hw, struct qmp, qdss_clk);
qmp_send(qmp, buf, sizeof(buf));
}
static const struct clk_ops qmp_qdss_clk_ops = {
.prepare = qmp_qdss_clk_prepare,
.unprepare = qmp_qdss_clk_unprepare,
};
static int qmp_qdss_clk_add(struct qmp *qmp)
{
static const struct clk_init_data qdss_init = {
.ops = &qmp_qdss_clk_ops,
.name = "qdss",
};
int ret;
qmp->qdss_clk.init = &qdss_init;
ret = clk_hw_register(qmp->dev, &qmp->qdss_clk);
if (ret < 0) {
dev_err(qmp->dev, "failed to register qdss clock\n");
return ret;
}
ret = of_clk_add_hw_provider(qmp->dev->of_node, of_clk_hw_simple_get,
&qmp->qdss_clk);
if (ret < 0) {
dev_err(qmp->dev, "unable to register of clk hw provider\n");
clk_hw_unregister(&qmp->qdss_clk);
}
return ret;
}
static void qmp_qdss_clk_remove(struct qmp *qmp)
{
of_clk_del_provider(qmp->dev->of_node);
clk_hw_unregister(&qmp->qdss_clk);
}
static int qmp_pd_power_toggle(struct qmp_pd *res, bool enable)
{
char buf[QMP_MSG_LEN] = {};
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_power_on(struct generic_pm_domain *domain)
{
return qmp_pd_power_toggle(to_qmp_pd_resource(domain), true);
}
static int qmp_pd_power_off(struct generic_pm_domain *domain)
{
return qmp_pd_power_toggle(to_qmp_pd_resource(domain), false);
}
static const char * const sdm845_resources[] = {
[AOSS_QMP_LS_CDSP] = "cdsp",
[AOSS_QMP_LS_LPASS] = "adsp",
[AOSS_QMP_LS_MODEM] = "modem",
[AOSS_QMP_LS_SLPI] = "slpi",
[AOSS_QMP_LS_SPSS] = "spss",
[AOSS_QMP_LS_VENUS] = "venus",
};
static int qmp_pd_add(struct qmp *qmp)
{
struct genpd_onecell_data *data = &qmp->pd_data;
struct device *dev = qmp->dev;
struct qmp_pd *res;
size_t num = ARRAY_SIZE(sdm845_resources);
int ret;
int i;
res = devm_kcalloc(dev, num, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
data->domains = devm_kcalloc(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];
res[i].pd.power_on = qmp_pd_power_on;
res[i].pd.power_off = qmp_pd_power_off;
ret = pm_genpd_init(&res[i].pd, NULL, true);
if (ret < 0) {
dev_err(dev, "failed to init genpd\n");
goto unroll_genpds;
}
data->domains[i] = &res[i].pd;
}
data->num_domains = i;
ret = of_genpd_add_provider_onecell(dev->of_node, data);
if (ret < 0)
goto unroll_genpds;
return 0;
unroll_genpds:
for (i--; i >= 0; i--)
pm_genpd_remove(data->domains[i]);
return ret;
}
static void qmp_pd_remove(struct qmp *qmp)
{
struct genpd_onecell_data *data = &qmp->pd_data;
struct device *dev = qmp->dev;
int i;
of_genpd_del_provider(dev->of_node);
for (i = 0; i < data->num_domains; i++)
pm_genpd_remove(data->domains[i]);
}
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_free_mbox;
}
ret = qmp_open(qmp);
if (ret < 0)
goto err_free_mbox;
ret = qmp_qdss_clk_add(qmp);
if (ret)
goto err_close_qmp;
ret = qmp_pd_add(qmp);
if (ret)
goto err_remove_qdss_clk;
platform_set_drvdata(pdev, qmp);
return 0;
err_remove_qdss_clk:
qmp_qdss_clk_remove(qmp);
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);
qmp_qdss_clk_remove(qmp);
qmp_pd_remove(qmp);
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 = "qcom_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");

View File

@ -16,56 +16,76 @@
#define domain_to_rpmpd(domain) container_of(domain, struct rpmpd, pd)
/* Resource types */
/* Resource types:
* RPMPD_X is X encoded as a little-endian, lower-case, ASCII string */
#define RPMPD_SMPA 0x61706d73
#define RPMPD_LDOA 0x616f646c
#define RPMPD_RWCX 0x78637772
#define RPMPD_RWMX 0x786d7772
#define RPMPD_RWLC 0x636c7772
#define RPMPD_RWLM 0x6d6c7772
#define RPMPD_RWSC 0x63737772
#define RPMPD_RWSM 0x6d737772
/* Operation Keys */
#define KEY_CORNER 0x6e726f63 /* corn */
#define KEY_ENABLE 0x6e657773 /* swen */
#define KEY_FLOOR_CORNER 0x636676 /* vfc */
#define KEY_FLOOR_LEVEL 0x6c6676 /* vfl */
#define KEY_LEVEL 0x6c766c76 /* vlvl */
#define MAX_RPMPD_STATE 6
#define MAX_8996_RPMPD_STATE 6
#define DEFINE_RPMPD_CORNER_SMPA(_platform, _name, _active, r_id) \
#define DEFINE_RPMPD_PAIR(_platform, _name, _active, r_type, r_key, \
r_id) \
static struct rpmpd _platform##_##_active; \
static struct rpmpd _platform##_##_name = { \
.pd = { .name = #_name, }, \
.peer = &_platform##_##_active, \
.res_type = RPMPD_SMPA, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_CORNER, \
.key = KEY_##r_key, \
}; \
static struct rpmpd _platform##_##_active = { \
.pd = { .name = #_active, }, \
.peer = &_platform##_##_name, \
.active_only = true, \
.res_type = RPMPD_SMPA, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_##r_key, \
}
#define DEFINE_RPMPD_CORNER(_platform, _name, r_type, r_id) \
static struct rpmpd _platform##_##_name = { \
.pd = { .name = #_name, }, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_CORNER, \
}
#define DEFINE_RPMPD_CORNER_LDOA(_platform, _name, r_id) \
#define DEFINE_RPMPD_LEVEL(_platform, _name, r_type, r_id) \
static struct rpmpd _platform##_##_name = { \
.pd = { .name = #_name, }, \
.res_type = RPMPD_LDOA, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_CORNER, \
.key = KEY_LEVEL, \
}
#define DEFINE_RPMPD_VFC(_platform, _name, r_id, r_type) \
#define DEFINE_RPMPD_VFC(_platform, _name, r_type, r_id) \
static struct rpmpd _platform##_##_name = { \
.pd = { .name = #_name, }, \
.res_type = r_type, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_FLOOR_CORNER, \
}
#define DEFINE_RPMPD_VFC_SMPA(_platform, _name, r_id) \
DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_SMPA)
#define DEFINE_RPMPD_VFC_LDOA(_platform, _name, r_id) \
DEFINE_RPMPD_VFC(_platform, _name, r_id, RPMPD_LDOA)
#define DEFINE_RPMPD_VFL(_platform, _name, r_type, r_id) \
static struct rpmpd _platform##_##_name = { \
.pd = { .name = #_name, }, \
.res_type = RPMPD_##r_type, \
.res_id = r_id, \
.key = KEY_FLOOR_LEVEL, \
}
struct rpmpd_req {
__le32 key;
@ -83,23 +103,25 @@ struct rpmpd {
const int res_type;
const int res_id;
struct qcom_smd_rpm *rpm;
unsigned int max_state;
__le32 key;
};
struct rpmpd_desc {
struct rpmpd **rpmpds;
size_t num_pds;
unsigned int max_state;
};
static DEFINE_MUTEX(rpmpd_lock);
/* msm8996 RPM Power domains */
DEFINE_RPMPD_CORNER_SMPA(msm8996, vddcx, vddcx_ao, 1);
DEFINE_RPMPD_CORNER_SMPA(msm8996, vddmx, vddmx_ao, 2);
DEFINE_RPMPD_CORNER_LDOA(msm8996, vddsscx, 26);
DEFINE_RPMPD_PAIR(msm8996, vddcx, vddcx_ao, SMPA, CORNER, 1);
DEFINE_RPMPD_PAIR(msm8996, vddmx, vddmx_ao, SMPA, CORNER, 2);
DEFINE_RPMPD_CORNER(msm8996, vddsscx, LDOA, 26);
DEFINE_RPMPD_VFC_SMPA(msm8996, vddcx_vfc, 1);
DEFINE_RPMPD_VFC_LDOA(msm8996, vddsscx_vfc, 26);
DEFINE_RPMPD_VFC(msm8996, vddcx_vfc, SMPA, 1);
DEFINE_RPMPD_VFC(msm8996, vddsscx_vfc, LDOA, 26);
static struct rpmpd *msm8996_rpmpds[] = {
[MSM8996_VDDCX] = &msm8996_vddcx,
@ -114,10 +136,71 @@ static struct rpmpd *msm8996_rpmpds[] = {
static const struct rpmpd_desc msm8996_desc = {
.rpmpds = msm8996_rpmpds,
.num_pds = ARRAY_SIZE(msm8996_rpmpds),
.max_state = MAX_8996_RPMPD_STATE,
};
/* msm8998 RPM Power domains */
DEFINE_RPMPD_PAIR(msm8998, vddcx, vddcx_ao, RWCX, LEVEL, 0);
DEFINE_RPMPD_VFL(msm8998, vddcx_vfl, RWCX, 0);
DEFINE_RPMPD_PAIR(msm8998, vddmx, vddmx_ao, RWMX, LEVEL, 0);
DEFINE_RPMPD_VFL(msm8998, vddmx_vfl, RWMX, 0);
DEFINE_RPMPD_LEVEL(msm8998, vdd_ssccx, RWSC, 0);
DEFINE_RPMPD_VFL(msm8998, vdd_ssccx_vfl, RWSC, 0);
DEFINE_RPMPD_LEVEL(msm8998, vdd_sscmx, RWSM, 0);
DEFINE_RPMPD_VFL(msm8998, vdd_sscmx_vfl, RWSM, 0);
static struct rpmpd *msm8998_rpmpds[] = {
[MSM8998_VDDCX] = &msm8998_vddcx,
[MSM8998_VDDCX_AO] = &msm8998_vddcx_ao,
[MSM8998_VDDCX_VFL] = &msm8998_vddcx_vfl,
[MSM8998_VDDMX] = &msm8998_vddmx,
[MSM8998_VDDMX_AO] = &msm8998_vddmx_ao,
[MSM8998_VDDMX_VFL] = &msm8998_vddmx_vfl,
[MSM8998_SSCCX] = &msm8998_vdd_ssccx,
[MSM8998_SSCCX_VFL] = &msm8998_vdd_ssccx_vfl,
[MSM8998_SSCMX] = &msm8998_vdd_sscmx,
[MSM8998_SSCMX_VFL] = &msm8998_vdd_sscmx_vfl,
};
static const struct rpmpd_desc msm8998_desc = {
.rpmpds = msm8998_rpmpds,
.num_pds = ARRAY_SIZE(msm8998_rpmpds),
.max_state = RPM_SMD_LEVEL_BINNING,
};
/* qcs404 RPM Power domains */
DEFINE_RPMPD_PAIR(qcs404, vddmx, vddmx_ao, RWMX, LEVEL, 0);
DEFINE_RPMPD_VFL(qcs404, vddmx_vfl, RWMX, 0);
DEFINE_RPMPD_LEVEL(qcs404, vdd_lpicx, RWLC, 0);
DEFINE_RPMPD_VFL(qcs404, vdd_lpicx_vfl, RWLC, 0);
DEFINE_RPMPD_LEVEL(qcs404, vdd_lpimx, RWLM, 0);
DEFINE_RPMPD_VFL(qcs404, vdd_lpimx_vfl, RWLM, 0);
static struct rpmpd *qcs404_rpmpds[] = {
[QCS404_VDDMX] = &qcs404_vddmx,
[QCS404_VDDMX_AO] = &qcs404_vddmx_ao,
[QCS404_VDDMX_VFL] = &qcs404_vddmx_vfl,
[QCS404_LPICX] = &qcs404_vdd_lpicx,
[QCS404_LPICX_VFL] = &qcs404_vdd_lpicx_vfl,
[QCS404_LPIMX] = &qcs404_vdd_lpimx,
[QCS404_LPIMX_VFL] = &qcs404_vdd_lpimx_vfl,
};
static const struct rpmpd_desc qcs404_desc = {
.rpmpds = qcs404_rpmpds,
.num_pds = ARRAY_SIZE(qcs404_rpmpds),
.max_state = RPM_SMD_LEVEL_BINNING,
};
static const struct of_device_id rpmpd_match_table[] = {
{ .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc },
{ .compatible = "qcom,msm8998-rpmpd", .data = &msm8998_desc },
{ .compatible = "qcom,qcs404-rpmpd", .data = &qcs404_desc },
{ }
};
@ -225,14 +308,16 @@ static int rpmpd_set_performance(struct generic_pm_domain *domain,
int ret = 0;
struct rpmpd *pd = domain_to_rpmpd(domain);
if (state > MAX_RPMPD_STATE)
goto out;
if (state > pd->max_state)
state = pd->max_state;
mutex_lock(&rpmpd_lock);
pd->corner = state;
if (!pd->enabled && pd->key != KEY_FLOOR_CORNER)
/* Always send updates for vfc and vfl */
if (!pd->enabled && pd->key != KEY_FLOOR_CORNER &&
pd->key != KEY_FLOOR_LEVEL)
goto out;
ret = rpmpd_aggregate_corner(pd);
@ -287,6 +372,7 @@ static int rpmpd_probe(struct platform_device *pdev)
}
rpmpds[i]->rpm = rpm;
rpmpds[i]->max_state = desc->max_state;
rpmpds[i]->pd.power_off = rpmpd_power_off;
rpmpds[i]->pd.power_on = rpmpd_power_on;
rpmpds[i]->pd.set_performance_state = rpmpd_set_performance;

View File

@ -0,0 +1,14 @@
/* 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_LS_CDSP 0
#define AOSS_QMP_LS_LPASS 1
#define AOSS_QMP_LS_MODEM 2
#define AOSS_QMP_LS_SLPI 3
#define AOSS_QMP_LS_SPSS 4
#define AOSS_QMP_LS_VENUS 5
#endif

View File

@ -36,4 +36,38 @@
#define MSM8996_VDDSSCX 5
#define MSM8996_VDDSSCX_VFC 6
/* MSM8998 Power Domain Indexes */
#define MSM8998_VDDCX 0
#define MSM8998_VDDCX_AO 1
#define MSM8998_VDDCX_VFL 2
#define MSM8998_VDDMX 3
#define MSM8998_VDDMX_AO 4
#define MSM8998_VDDMX_VFL 5
#define MSM8998_SSCCX 6
#define MSM8998_SSCCX_VFL 7
#define MSM8998_SSCMX 8
#define MSM8998_SSCMX_VFL 9
/* QCS404 Power Domains */
#define QCS404_VDDMX 0
#define QCS404_VDDMX_AO 1
#define QCS404_VDDMX_VFL 2
#define QCS404_LPICX 3
#define QCS404_LPICX_VFL 4
#define QCS404_LPIMX 5
#define QCS404_LPIMX_VFL 6
/* RPM SMD Power Domain performance levels */
#define RPM_SMD_LEVEL_RETENTION 16
#define RPM_SMD_LEVEL_RETENTION_PLUS 32
#define RPM_SMD_LEVEL_MIN_SVS 48
#define RPM_SMD_LEVEL_LOW_SVS 64
#define RPM_SMD_LEVEL_SVS 128
#define RPM_SMD_LEVEL_SVS_PLUS 192
#define RPM_SMD_LEVEL_NOM 256
#define RPM_SMD_LEVEL_NOM_PLUS 320
#define RPM_SMD_LEVEL_TURBO 384
#define RPM_SMD_LEVEL_TURBO_NO_CPR 416
#define RPM_SMD_LEVEL_BINNING 512
#endif