From mboxrd@z Thu Jan 1 00:00:00 1970 From: Kumar Gala Subject: Re: [PATCH RFC] WIP: mfd: add support for Qualcomm RPM Date: Fri, 11 Apr 2014 10:54:45 -0500 Message-ID: References: <1397168269-6844-1-git-send-email-joshc@codeaurora.org> Mime-Version: 1.0 (Mac OS X Mail 7.2 \(1874\)) Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: QUOTED-PRINTABLE Return-path: Received: from smtp.codeaurora.org ([198.145.11.231]:42768 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754219AbaDKPyt convert rfc822-to-8bit (ORCPT ); Fri, 11 Apr 2014 11:54:49 -0400 In-Reply-To: <1397168269-6844-1-git-send-email-joshc@codeaurora.org> Sender: linux-arm-msm-owner@vger.kernel.org List-Id: linux-arm-msm@vger.kernel.org To: Josh Cartwright Cc: linux-arm-msm@vger.kernel.org, Bjorn Andersson On Apr 10, 2014, at 5:17 PM, Josh Cartwright wro= te: > 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. >=20 > 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 add= ing > device tree support. >=20 > [1]: https://www.codeaurora.org/cgit/quic/la/kernel/msm/tree/arch/arm= /mach-msm/rpm.c?h=3Dmsm-3.4 >=20 > Signed-off-by: Josh Cartwright > --- > 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. >=20 > I've chosen a path where a subnode logically separates the RPM resour= ces; 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 r= esources > described in 'reg'. >=20 > Effectively, this pushes the "generic resource ID" -> "SoC-specific r= esource > ID" mapping out of the large data tables that exist in msm-3.4 into t= he device > tree. An alternative approach would be to still maintain the SoC-spe= cific > tables, and have each node matched to it's resources using a unique c= ompatible > string. >=20 > Any comments appreciated! >=20 > 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 >=20 > diff --git a/Documentation/devicetree/bindings/mfd/qcom,rpm.txt b/Doc= umentation/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 pendin= g request I assume this varies between apq8064 and ipq8064, I think it might be b= etter just encoded in the .data field of the of_device_id table. Probably need some description about the child/subnode you=92ve 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 registe= r, > + this cell is ignored (and should be set to zero) when type is > + RPM_TYPE_STATUS > + > +Example: > + > + #include > + > + rpm@108000 { > + compatible =3D "qcom,rpm-ipq8064"; > + reg =3D <0x00108000 0x1000>, > + <0x02011008 0x4>; > + reg-names =3D "msg_ram", > + "ipc"; > + interrupts =3D , > + , > + ; > + interrupt-names =3D "ack", > + "err", > + "wakeup"; > + ipc-bit =3D <2>; > + > + #address-cells =3D <3>; > + #size-cells =3D <0>; > + > + subnode { > + compatible =3D "..."; > + reg =3D <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 GP= IO > and MPP. >=20 > +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 SoC= s. > + 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) +=3D cs5535-mfd.o > obj-$(CONFIG_MFD_OMAP_USB_HOST) +=3D omap-usb-host.o omap-usb-tll.o > obj-$(CONFIG_MFD_PM8921_CORE) +=3D pm8921-core.o ssbi.o > obj-$(CONFIG_MFD_PM8XXX_IRQ) +=3D pm8xxx-irq.o > +obj-$(CONFIG_MFD_QCOM_RPM) +=3D qcom-rpm.o > obj-$(CONFIG_TPS65911_COMPARATOR) +=3D tps65911-comparator.o > obj-$(CONFIG_MFD_TPS65090) +=3D tps65090.o > obj-$(CONFIG_MFD_AAT2870_CORE) +=3D 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 res= erved. > + * > + * This program is free software; you can redistribute it and/or mod= ify > + * it under the terms of the GNU General Public License version 2 an= d > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +#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_m= ask ctx, > + const struct qcom_rpm_req *req, u32 *data, size_t len) > +{ > + u32 __iomem *req_sel_reg =3D rpm->ctrl + RPM_CONTROL_REQ_SEL; > + u32 sel_masks[5] =3D { }, sel_masks_ack[5]; > + u32 ctx_ack; > + size_t i; > + > + for (i =3D 0; i < len; i++) > + sel_masks[req->sel_reg] |=3D req->sel_mask; > + > + mutex_lock(&rpm->lock); > + > + rpm->ctx_ack =3D &ctx_ack; > + rpm->sel_masks_ack =3D &sel_masks_ack; > + > + for (i =3D 0; i < len; i++) > + writel_relaxed(data[i], rpm->req + req[i].offset); > + > + for (i =3D 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 =3D 0; i < rpm->num_req; i++) > + data[i] =3D readl_relaxed(rpm->ack + rpm->pending_req[i].offset); > + > + mutex_unlock(&rpm->lock); > + > + if (ctx_ack & QCOM_RPM_CTX_REJECTED) > + return -ENOSPC; > + > + ctx_ack &=3D ~QCOM_RPM_CTX_REJECTED; > + if (WARN_ON(ctx_ack !=3D 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 =3D devid; > + u32 __iomem *ack_sel_reg =3D rpm->ctrl + RPM_CONTROL_ACK_SEL; > + unsigned int i; > + > + *rpm->ctx_ack =3D readl_relaxed(rpm->ctrl + RPM_CONTROL_ACK_CTX); > + > + for (i =3D 0; i < ARRAY_SIZE(*rpm->sel_masks_ack); i++) { > + *rpm->sel_masks_ack[i] =3D 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 =3D 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 *pd= ev, > + unsigned int which, unsigned int type) > +{ > + const struct device_node *np =3D pdev->dev.of_node; > + const __be32 *cell; > + int sz; > + > + cell =3D of_get_property(np, "reg", &sz); > + if (!cell) > + return ERR_PTR(-EINVAL); > + > + sz /=3D 3 * sizeof(u32); > + > + for (; sz--; cell +=3D 3) { > + > + if (be32_to_cpup(&cell[1]) !=3D type) > + continue; > + > + if (!which--) > + return cell; > + > + } > + > + return ERR_PTR(-ENOENT); > +} > + > +int qcom_rpm_get_req(struct platform_device *pdev, unsigned int whic= h, > + struct qcom_rpm_req *req) > +{ > + const __be32 *cell; > + u32 sel_bit; > + > + cell =3D qcom_decode_reg_type(pdev, which, RPM_TYPE_REQ); > + if (IS_ERR(cell)) > + return PTR_ERR(cell); > + > + req->offset =3D be32_to_cpup(&cell[0]); > + > + sel_bit =3D be32_to_cpup(&cell[2]); > + > + req->sel_reg =3D sel_bit / 32; > + req->sel_mask =3D 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 =3D qcom_rpm_get(pdev); > + const __be32 *cell; > + > + cell =3D 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 =3D readl_relaxed(rpm->status + RPM_STATUS_VERSION_MAJOR= ); > + > + if (vers_major !=3D 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 =3D devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL); > + if (!rpm) > + return -ENOMEM; > + > + rpm->dev =3D &pdev->dev; > + > + res =3D platform_get_resource_byname(pdev, IORESOURCE_MEM, "msg_ram= "); > + rpm->status =3D devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(rpm->status)) > + return PTR_ERR(rpm->status); > + > + rpm->ctrl =3D rpm->status + 0x400; > + rpm->req =3D rpm->status + 0x600; > + rpm->ack =3D rpm->status + 0xA00; > + > + err =3D qcom_rpm_check_version(rpm); > + if (err) > + return err; > + > + init_completion(&rpm->done); > + mutex_init(&rpm->lock); > + > + res =3D platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipc"); > + rpm->ipc_reg =3D devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(rpm->ipc_reg)) > + return PTR_ERR(rpm->ipc_reg); > + > + err =3D 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 =3D BIT(bit); > + > + irq =3D platform_get_irq_byname(pdev, "ack"); > + if (irq < 0) { > + dev_err(&pdev->dev, "invalid ack interrupt specified.\n"); > + return irq; > + } > + > + err =3D 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 =3D platform_get_irq_byname(pdev, "err"); > + if (irq < 0) { > + dev_err(&pdev->dev, "invalid err interrupt specified.\n"); > + return irq; > + } > + > + err =3D 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->d= ev); > +} > + > +static const struct of_device_id qcom_rpm_of_match[] =3D { > + { .compatible =3D "qcom,rpm-apq8064", }, > + { .compatible =3D "qcom,rpm-ipq8064", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, qcom_rpm_of_match); > + > +static struct platform_driver msm_rpm_platform_driver =3D { > + .probe =3D qcom_rpm_probe, > + .driver =3D { > + .name =3D "qcom_rpm", > + .of_match_table =3D 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_rp= m.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 mod= ify > + * it under the terms of the GNU General Public License version 2 an= d > + * 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 > + > +struct qcom_rpm; > + > +static inline struct qcom_rpm *qcom_rpm_get(struct platform_device *= pdev) > +{ > + struct platform_device *parent; > + > + parent =3D 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 =3D BIT(0), > + QCOM_RPM_CTX_SET_SLEEP =3D BIT(1), > + QCOM_RPM_CTX_NOTIFICATION =3D BIT(30), > + QCOM_RPM_CTX_REJECTED =3D BIT(31), > +}; > + > +int qcom_rpm_write_ctx(struct qcom_rpm *rpm, enum qcom_rpm_context_m= ask 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 whic= h, > + struct qcom_rpm_req *req); > + > +const void __iomem *qcom_rpm_get_status(struct platform_device *pdev= , > + unsigned int which); > + > +#endif /* QCOM_RPM_H */ > --=20 > Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, > hosted by The Linux Foundation >=20 > -- > To unsubscribe from this list: send the line "unsubscribe linux-arm-m= sm" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html --=20 Employee of Qualcomm Innovation Center, Inc. Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, host= ed by The Linux Foundation