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@xxxxxxxxxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html