All of lore.kernel.org
 help / color / mirror / Atom feed
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

             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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.