On Apr 10, 2014, at 5:17 PM, Josh Cartwright <[email protected]> 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 <[email protected]>
> ---
> 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 [email protected]
> 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 [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to