From: Josh Cartwright <joshc@codeaurora.org>
To: linux-arm-msm@vger.kernel.org
Cc: Bjorn Andersson <bjorn.andersson@sonymobile.com>
Subject: [PATCH RFC] WIP: mfd: add support for Qualcomm RPM
Date: Thu, 10 Apr 2014 17:17:49 -0500 [thread overview]
Message-ID: <1397168269-6844-1-git-send-email-joshc@codeaurora.org> (raw)
The Resource Power Manager (RPM) is responsible managing SoC-wide
resources (clocks, regulators, etc) on MSM and other Qualcomm SoCs.
This driver provides an implementation of the message-RAM-based
communication protocol.
Note, this is a rewrite of the driver as it exists in the downstream
tree[1], making a few simplifying assumptions to clean it up, and adding
device tree support.
[1]: https://www.codeaurora.org/cgit/quic/la/kernel/msm/tree/arch/arm/mach-msm/rpm.c?h=msm-3.4
Signed-off-by: Josh Cartwright <joshc@codeaurora.org>
---
This patch is intended to act as a starting point for discussions on how we
should proceed going forward supporting RPM. In particular, figuring out how
to model RPM and it's controlled resources in device tree.
I've chosen a path where a subnode logically separates the RPM resources; it's
intended each set of resources will be controlled by a single driver. For
example, an RPM-controlled regulator might consume two RPM_TYPE_REQ resources
described in 'reg'.
Effectively, this pushes the "generic resource ID" -> "SoC-specific resource
ID" mapping out of the large data tables that exist in msm-3.4 into the device
tree. An alternative approach would be to still maintain the SoC-specific
tables, and have each node matched to it's resources using a unique compatible
string.
Any comments appreciated!
Thanks,
Josh
Documentation/devicetree/bindings/mfd/qcom,rpm.txt | 68 +++++
drivers/mfd/Kconfig | 9 +
drivers/mfd/Makefile | 1 +
drivers/mfd/qcom-rpm.c | 314 +++++++++++++++++++++
include/linux/mfd/qcom_rpm.h | 64 +++++
5 files changed, 456 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/qcom,rpm.txt
create mode 100644 drivers/mfd/qcom-rpm.c
create mode 100644 include/linux/mfd/qcom_rpm.h
diff --git a/Documentation/devicetree/bindings/mfd/qcom,rpm.txt b/Documentation/devicetree/bindings/mfd/qcom,rpm.txt
new file mode 100644
index 0000000..617018f
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/qcom,rpm.txt
@@ -0,0 +1,68 @@
+Qualcomm Resource Power Manager (RPM)
+
+This driver is used to interface with Resource Power Manager (RPM). The RPM is
+responsible managing SoC-wide resources (clocks, regulators, etc) on MSM and
+other Qualcomm chipsets.
+
+Required properties:
+
+- compatible: must be one of:
+ "qcom,rpm-apq8064"
+ "qcom,rpm-ipq8064"
+
+- reg: must contain two register specifiers, in the following order:
+ specifier 0: RPM Message RAM
+ specifier 1: IPC register
+
+- reg-names: must contain the following, in order:
+ "msg_ram"
+ "ipc"
+
+- interrupts: must contain the following three interrupt specifiers, in order:
+ specifier 0: RPM Acknowledgement Interrupt
+ specifier 1: Error Interrupt
+ specifier 2: Wakeup interrupt
+
+- interrupt-names: must contain the following, in order:
+ "ack"
+ "err"
+ "wakeup"
+
+- ipc-bit: bit written to the IPC register to notify RPM of a pending request
+
+- #address-cells: must be 3
+ cell 0: offset in ACK and REQ register spaces corresponding to the register
+ cell 1: type field, one of RPM_TYPE_REQ (0) or RPM_TYPE_STATUS (1)
+ cell 2: indicates the selector bit to set when writing this register,
+ this cell is ignored (and should be set to zero) when type is
+ RPM_TYPE_STATUS
+
+Example:
+
+ #include <dt-bindings/mfd/qcom_rpm.h>
+
+ rpm@108000 {
+ compatible = "qcom,rpm-ipq8064";
+ reg = <0x00108000 0x1000>,
+ <0x02011008 0x4>;
+ reg-names = "msg_ram",
+ "ipc";
+ interrupts = <GIC_SPI 19 0>,
+ <GIC_SPI 21 0>,
+ <GIC_SPI 22 0>;
+ interrupt-names = "ack",
+ "err",
+ "wakeup";
+ ipc-bit = <2>;
+
+ #address-cells = <3>;
+ #size-cells = <0>;
+
+ subnode {
+ compatible = "...";
+ reg = <464 RPM_TYPE_REQ 30>,
+ <468 RPM_TYPE_REQ 30>,
+ <118 RPM_TYPE_STATUS 0>;
+ };
+ };
+
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 49bb445..b387ba9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -497,6 +497,15 @@ config MFD_PM8XXX_IRQ
This is required to use certain other PM 8xxx features, such as GPIO
and MPP.
+config MFD_QCOM_RPM
+ tristate "Qualcomm Resource Power Manager (RPM) driver"
+ depends on (ARCH_QCOM || COMPILE_TEST)
+ help
+ The Resource Power Manager (RPM) is responsible managing SoC-wide
+ resources (clocks, regulators, etc) on MSM and other Qualcomm SoCs.
+ This driver provides an implementation of the message-RAM-based
+ communication protocol.
+
config MFD_RDC321X
tristate "RDC R-321x southbridge"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5aea5ef..a51fe46 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -151,6 +151,7 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o
obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o
obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o
+obj-$(CONFIG_MFD_QCOM_RPM) += qcom-rpm.o
obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
obj-$(CONFIG_MFD_TPS65090) += tps65090.o
obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
diff --git a/drivers/mfd/qcom-rpm.c b/drivers/mfd/qcom-rpm.c
new file mode 100644
index 0000000..ff33bc6
--- /dev/null
+++ b/drivers/mfd/qcom-rpm.c
@@ -0,0 +1,314 @@
+/* Copyright (c) 2010-2012,2014 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#define RPM_SUPPORTED_VERS_MAJOR 3
+
+#define RPM_STATUS_VERSION_MAJOR 0
+
+#define RPM_CONTROL_VERSION_MAJOR 0x00
+#define RPM_CONTROL_VERSION_MINOR 0x04
+#define RPM_CONTROL_VERSION_BUILD 0x08
+#define RPM_CONTROL_REQ_CTX 0x0C
+#define RPM_CONTROL_REQ_SEL 0x2C
+#define RPM_CONTROL_ACK_CTX 0x3C
+#define RPM_CONTROL_ACK_SEL 0x5C
+
+struct qcom_rpm {
+ struct device *dev;
+ void __iomem *status;
+ void __iomem *ctrl;
+ void __iomem *req;
+ void __iomem *ack;
+ void __iomem *ipc_reg;
+ u32 ipc_val;
+ u32 *ctx_ack;
+ u32 (*sel_masks_ack)[5];
+ struct qcom_rpm_req *pending_req;
+ size_t num_req;
+ struct completion done;
+ struct mutex lock;
+};
+
+static void qcom_rpm_kick(struct qcom_rpm *rpm)
+{
+ writel(rpm->ipc_val, rpm->ipc_reg);
+}
+
+int qcom_rpm_write_ctx(struct qcom_rpm *rpm, enum qcom_rpm_context_mask ctx,
+ const struct qcom_rpm_req *req, u32 *data, size_t len)
+{
+ u32 __iomem *req_sel_reg = rpm->ctrl + RPM_CONTROL_REQ_SEL;
+ u32 sel_masks[5] = { }, sel_masks_ack[5];
+ u32 ctx_ack;
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ sel_masks[req->sel_reg] |= req->sel_mask;
+
+ mutex_lock(&rpm->lock);
+
+ rpm->ctx_ack = &ctx_ack;
+ rpm->sel_masks_ack = &sel_masks_ack;
+
+ for (i = 0; i < len; i++)
+ writel_relaxed(data[i], rpm->req + req[i].offset);
+
+ for (i = 0; i < ARRAY_SIZE(sel_masks); i++)
+ writel_relaxed(sel_masks[i], &req_sel_reg[i]);
+
+ writel_relaxed(ctx, rpm->ctrl + RPM_CONTROL_REQ_CTX);
+
+ qcom_rpm_kick(rpm);
+
+ wait_for_completion(&rpm->done);
+ reinit_completion(&rpm->done);
+
+ for (i = 0; i < rpm->num_req; i++)
+ data[i] = readl_relaxed(rpm->ack + rpm->pending_req[i].offset);
+
+ mutex_unlock(&rpm->lock);
+
+ if (ctx_ack & QCOM_RPM_CTX_REJECTED)
+ return -ENOSPC;
+
+ ctx_ack &= ~QCOM_RPM_CTX_REJECTED;
+ if (WARN_ON(ctx_ack != ctx)) {
+ dev_err(rpm->dev, "received bad context ack.\n");
+ return -EFAULT;
+ }
+
+ if (WARN_ON(memcmp(sel_masks, sel_masks_ack, sizeof(sel_masks)))) {
+ dev_err(rpm->dev,
+ "requested writes failed to be acknowledged.\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(qcom_rpm_write_ctx);
+
+static irqreturn_t qcom_rpm_ack_irq(int irq, void *devid)
+{
+ struct qcom_rpm *rpm = devid;
+ u32 __iomem *ack_sel_reg = rpm->ctrl + RPM_CONTROL_ACK_SEL;
+ unsigned int i;
+
+ *rpm->ctx_ack = readl_relaxed(rpm->ctrl + RPM_CONTROL_ACK_CTX);
+
+ for (i = 0; i < ARRAY_SIZE(*rpm->sel_masks_ack); i++) {
+ *rpm->sel_masks_ack[i] = readl_relaxed(&ack_sel_reg[i]);
+ writel_relaxed(0, &ack_sel_reg[i]);
+ }
+
+ writel_relaxed(0, rpm->ctrl + RPM_CONTROL_ACK_CTX);
+
+ /* Ignore notifications for now */
+ if (*rpm->ctx_ack & QCOM_RPM_CTX_NOTIFICATION)
+ return IRQ_HANDLED;
+
+ complete(&rpm->done);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_rpm_err_irq(int irq, void *devid)
+{
+ struct qcom_rpm *rpm = devid;
+
+ WARN(1, "RPM triggered fatal error. RPM communication unreliable.");
+ writel_relaxed(1, rpm->ipc_reg);
+
+ return IRQ_HANDLED;
+}
+
+static const __be32 *qcom_decode_reg_type(struct platform_device *pdev,
+ unsigned int which, unsigned int type)
+{
+ const struct device_node *np = pdev->dev.of_node;
+ const __be32 *cell;
+ int sz;
+
+ cell = of_get_property(np, "reg", &sz);
+ if (!cell)
+ return ERR_PTR(-EINVAL);
+
+ sz /= 3 * sizeof(u32);
+
+ for (; sz--; cell += 3) {
+
+ if (be32_to_cpup(&cell[1]) != type)
+ continue;
+
+ if (!which--)
+ return cell;
+
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+
+int qcom_rpm_get_req(struct platform_device *pdev, unsigned int which,
+ struct qcom_rpm_req *req)
+{
+ const __be32 *cell;
+ u32 sel_bit;
+
+ cell = qcom_decode_reg_type(pdev, which, RPM_TYPE_REQ);
+ if (IS_ERR(cell))
+ return PTR_ERR(cell);
+
+ req->offset = be32_to_cpup(&cell[0]);
+
+ sel_bit = be32_to_cpup(&cell[2]);
+
+ req->sel_reg = sel_bit / 32;
+ req->sel_mask = BIT(sel_bit % 32);
+ return 0;
+}
+EXPORT_SYMBOL(qcom_rpm_get_req);
+
+const void __iomem *qcom_rpm_get_status(struct platform_device *pdev,
+ unsigned int which)
+{
+ struct qcom_rpm *rpm = qcom_rpm_get(pdev);
+ const __be32 *cell;
+
+ cell = qcom_decode_reg_type(pdev, which, RPM_TYPE_STATUS);
+ if (IS_ERR(cell))
+ return (const void __iomem *) cell;
+
+ return rpm->status + be32_to_cpup(&cell[0]);
+}
+EXPORT_SYMBOL(qcom_rpm_get_status);
+
+static int qcom_rpm_check_version(struct qcom_rpm *rpm)
+{
+ u32 vers_major;
+
+ vers_major = readl_relaxed(rpm->status + RPM_STATUS_VERSION_MAJOR);
+
+ if (vers_major != RPM_SUPPORTED_VERS_MAJOR) {
+ dev_err(rpm->dev, "RPM driver does not support firmware with major version %d\n",
+ vers_major);
+ return -EINVAL;
+ }
+
+ writel_relaxed(vers_major, rpm->ctrl + RPM_CONTROL_VERSION_MAJOR);
+ writel_relaxed(0, rpm->ctrl + RPM_CONTROL_VERSION_MINOR);
+ writel_relaxed(0, rpm->ctrl + RPM_CONTROL_VERSION_BUILD);
+ return 0;
+}
+
+static int qcom_rpm_probe(struct platform_device *pdev)
+{
+ struct qcom_rpm *rpm;
+ struct resource *res;
+ int err, irq;
+ u32 bit;
+
+ rpm = devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL);
+ if (!rpm)
+ return -ENOMEM;
+
+ rpm->dev = &pdev->dev;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "msg_ram");
+ rpm->status = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(rpm->status))
+ return PTR_ERR(rpm->status);
+
+ rpm->ctrl = rpm->status + 0x400;
+ rpm->req = rpm->status + 0x600;
+ rpm->ack = rpm->status + 0xA00;
+
+ err = qcom_rpm_check_version(rpm);
+ if (err)
+ return err;
+
+ init_completion(&rpm->done);
+ mutex_init(&rpm->lock);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipc");
+ rpm->ipc_reg = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(rpm->ipc_reg))
+ return PTR_ERR(rpm->ipc_reg);
+
+ err = of_property_read_u32(pdev->dev.of_node, "ipc-bit", &bit);
+ if (err) {
+ dev_err(&pdev->dev, "ipc-bit property unspecified.\n");
+ return -EINVAL;
+ }
+
+ if (bit > 31) {
+ dev_err(&pdev->dev, "invalid ipc-bit specified.\n");
+ return -EINVAL;
+ }
+
+ rpm->ipc_val = BIT(bit);
+
+ irq = platform_get_irq_byname(pdev, "ack");
+ if (irq < 0) {
+ dev_err(&pdev->dev, "invalid ack interrupt specified.\n");
+ return irq;
+ }
+
+ err = devm_request_irq(&pdev->dev, irq, qcom_rpm_ack_irq,
+ IRQF_TRIGGER_RISING, "rpm_ack", rpm);
+ if (err) {
+ dev_err(&pdev->dev, "unable to request ack interrupt\n");
+ return err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "err");
+ if (irq < 0) {
+ dev_err(&pdev->dev, "invalid err interrupt specified.\n");
+ return irq;
+ }
+
+ err = devm_request_irq(&pdev->dev, irq, qcom_rpm_err_irq,
+ IRQF_TRIGGER_RISING, "rpm_err", rpm);
+ if (err) {
+ dev_err(&pdev->dev, "unable to request err interrupt\n");
+ return err;
+ }
+
+ platform_set_drvdata(pdev, rpm);
+
+ return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+}
+
+static const struct of_device_id qcom_rpm_of_match[] = {
+ { .compatible = "qcom,rpm-apq8064", },
+ { .compatible = "qcom,rpm-ipq8064", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_rpm_of_match);
+
+static struct platform_driver msm_rpm_platform_driver = {
+ .probe = qcom_rpm_probe,
+ .driver = {
+ .name = "qcom_rpm",
+ .of_match_table = qcom_rpm_of_match,
+ },
+};
+module_platform_driver(msm_rpm_platform_driver);
diff --git a/include/linux/mfd/qcom_rpm.h b/include/linux/mfd/qcom_rpm.h
new file mode 100644
index 0000000..1f585bf
--- /dev/null
+++ b/include/linux/mfd/qcom_rpm.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2014 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#ifndef QCOM_RPM_H
+#define QCOM_RPM_H
+
+#include <linux/platform_device.h>
+
+struct qcom_rpm;
+
+static inline struct qcom_rpm *qcom_rpm_get(struct platform_device *pdev)
+{
+ struct platform_device *parent;
+
+ parent = to_platform_device(pdev->dev.parent);
+
+ return platform_get_drvdata(parent);
+}
+
+struct qcom_rpm_req {
+ unsigned int offset;
+ unsigned int sel_reg;
+ u32 sel_mask;
+};
+
+enum qcom_rpm_context_mask {
+ QCOM_RPM_CTX_SET_ACTIVE = BIT(0),
+ QCOM_RPM_CTX_SET_SLEEP = BIT(1),
+ QCOM_RPM_CTX_NOTIFICATION = BIT(30),
+ QCOM_RPM_CTX_REJECTED = BIT(31),
+};
+
+int qcom_rpm_write_ctx(struct qcom_rpm *rpm, enum qcom_rpm_context_mask ctx,
+ const struct qcom_rpm_req *req, u32 *data, size_t len);
+
+static inline int qcom_rpm_req_write(struct qcom_rpm *rpm,
+ const struct qcom_rpm_req *req, u32 data)
+{
+ return qcom_rpm_write_ctx(rpm, QCOM_RPM_CTX_SET_ACTIVE, req, &data, 1);
+}
+
+static inline int qcom_rpm_req_write_sleep(struct qcom_rpm *rpm,
+ const struct qcom_rpm_req *req,
+ u32 data)
+{
+ return qcom_rpm_write_ctx(rpm, QCOM_RPM_CTX_SET_SLEEP, req, &data, 1);
+}
+
+int qcom_rpm_get_req(struct platform_device *pdev, unsigned int which,
+ struct qcom_rpm_req *req);
+
+const void __iomem *qcom_rpm_get_status(struct platform_device *pdev,
+ unsigned int which);
+
+#endif /* QCOM_RPM_H */
--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation
next reply other threads:[~2014-04-10 22:20 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-04-10 22:17 Josh Cartwright [this message]
2014-04-11 15:54 ` [PATCH RFC] WIP: mfd: add support for Qualcomm RPM Kumar Gala
2014-04-22 17:53 ` Bjorn Andersson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1397168269-6844-1-git-send-email-joshc@codeaurora.org \
--to=joshc@codeaurora.org \
--cc=bjorn.andersson@sonymobile.com \
--cc=linux-arm-msm@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).