On Apr 10, 2014, at 5:17 PM, Josh Cartwright <joshc@xxxxxxxxxxxxxx> wrote: > 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 I assume this varies between apq8064 and ipq8064, I think it might be better just encoded in the .data field of the of_device_id table. Probably need some description about the child/subnode you’ve got. > + > +- #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 -- Employee of Qualcomm Innovation Center, Inc. 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