msm: pil-q6v5-lpass: Add PIL support for copper's LPASS subsystem
The pil-q6v5-lpass driver is used to bring Low Power Audio Subsystems
(LPASS) containing QDSP6v5 processors out of reset and start their
execution of code. It also supports shutting down these subsystem to
save power.
Functions in the pil-q6v5 library are called to deal with the QDSP6v5-
specific portions of these operation, since that hardware is also
instantiated in other subsystems besides LPASS.
Change-Id: If2cf34d1bd859eaa28ef56a4cb26ea513c11f27a
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt b/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt
new file mode 100644
index 0000000..002431a
--- /dev/null
+++ b/Documentation/devicetree/bindings/pil/pil-q6v5-lpass.txt
@@ -0,0 +1,24 @@
+Qualcomm LPASS QDSP6v5 Peripheral Image Loader
+
+pil-qdsp6v5-lpass is a peripheral image loader (PIL) driver. It is used for
+loading QDSP6v5 (Hexagon) firmware images for Low Power Audio Subsystems
+into memory and preparing the subsystem's processor to execute code. It's
+also responsible for shutting down the processor when it's not needed.
+
+Required properties:
+- compatible: Must be "qcom,pil-q6v5-lpass"
+- reg: Three pairs of physical base addresses and region sizes
+ of memory mapped registers. The first region corresponds
+ to QDSP6SS_PUB, the second corresponds to LPASS_CC, and
+ the third to LPASS_HALTREQ.
+- qcom,firmware-name: Base name of the firmware image. Ex. "lpass"
+
+Example:
+ qcom,lpass@fe200000 {
+ compatible = "qcom,pil-q6v5-lpass";
+ reg = <0xfe200000 0x00100>,
+ <0xfe000000 0x40000>,
+ <0xfd485100 0x00010>;
+
+ qcom,firmware-name = "lpass";
+ };
diff --git a/arch/arm/boot/dts/msmcopper.dtsi b/arch/arm/boot/dts/msmcopper.dtsi
index 5a4b08f..4821290 100644
--- a/arch/arm/boot/dts/msmcopper.dtsi
+++ b/arch/arm/boot/dts/msmcopper.dtsi
@@ -243,4 +243,13 @@
interrupts = <0 131 0>;
qcom,dwc-usb3-msm-dbm-eps = <4>;
};
+
+ qcom,lpass@fe200000 {
+ compatible = "qcom,pil-q6v5-lpass";
+ reg = <0xfe200000 0x00100>,
+ <0xfe000000 0x40000>,
+ <0xfd485100 0x00010>;
+
+ qcom,firmware-name = "lpass";
+ };
};
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 4ea24a4..332b6e3 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -239,6 +239,7 @@
select MSM_GPIOMUX
select MULTI_IRQ_HANDLER
select MSM_MULTIMEDIA_USE_ION
+ select MSM_PIL
config ARCH_FSM9XXX
bool "FSM9XXX"
@@ -1742,7 +1743,6 @@
config MSM_PIL
bool "Peripheral image loading"
select FW_LOADER
- depends on (ARCH_MSM8X60 || ARCH_MSM8960)
default n
help
Some peripherals need to be loaded into memory before they can be
@@ -1771,6 +1771,13 @@
The QDSP6 is a low power DSP used in audio, modem firmware, and modem
software applications.
+config MSM_PIL_LPASS_QDSP6V5
+ tristate "LPASS QDSP6v5 (Hexagon) Boot Support"
+ depends on MSM_PIL
+ help
+ Support for booting and shutting down QDSP6v5 processors (Hexagon)
+ processors in low power audio subsystems.
+
config MSM_PIL_RIVA
tristate "RIVA (WCNSS) Boot Support"
depends on MSM_PIL
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index 1bd91a8..84af813 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -67,6 +67,7 @@
obj-$(CONFIG_MSM_PIL) += scm-pas.o
obj-$(CONFIG_MSM_PIL_QDSP6V3) += pil-q6v3.o
obj-$(CONFIG_MSM_PIL_QDSP6V4) += pil-q6v4.o
+obj-$(CONFIG_MSM_PIL_LPASS_QDSP6V5) += pil-q6v5.o pil-q6v5-lpass.o
obj-$(CONFIG_MSM_PIL_RIVA) += pil-riva.o
obj-$(CONFIG_MSM_PIL_TZAPPS) += pil-tzapps.o
obj-$(CONFIG_MSM_PIL_VIDC) += pil-vidc.o
diff --git a/arch/arm/mach-msm/board-copper.c b/arch/arm/mach-msm/board-copper.c
index d81ad92..ccfc770 100644
--- a/arch/arm/mach-msm/board-copper.c
+++ b/arch/arm/mach-msm/board-copper.c
@@ -456,6 +456,8 @@
"msm_sdcc.1", NULL),
OF_DEV_AUXDATA("qcom,msm-sdcc", 0xF984B000, \
"msm_sdcc.3", NULL),
+ OF_DEV_AUXDATA("qcom,pil-q6v5-lpass", 0xFE200000, \
+ "pil-q6v5-lpass", NULL),
{}
};
diff --git a/arch/arm/mach-msm/pil-q6v5-lpass.c b/arch/arm/mach-msm/pil-q6v5-lpass.c
new file mode 100644
index 0000000..60ae4d9
--- /dev/null
+++ b/arch/arm/mach-msm/pil-q6v5-lpass.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2012, Code Aurora Forum. 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/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/err.h>
+#include <linux/of.h>
+
+#include "peripheral-loader.h"
+#include "pil-q6v5.h"
+
+/* Register Offsets */
+#define QDSP6SS_RST_EVB 0x010
+#define LPASS_Q6SS_BCR 0x06000
+#define LPASS_Q6SS_AHB_LFABIF_CBCR 0x22000
+#define LPASS_Q6SS_XO_CBCR 0x26000
+#define AXI_HALTREQ 0x0
+#define AXI_HALTACK 0x4
+#define AXI_IDLE 0x8
+
+#define HALT_ACK_TIMEOUT_US 100000
+
+static void clk_reg_enable(void __iomem *reg)
+{
+ u32 val;
+ val = readl_relaxed(reg);
+ val |= BIT(0);
+ writel_relaxed(val, reg);
+}
+
+static void clk_reg_disable(void __iomem *reg)
+{
+ u32 val;
+ val = readl_relaxed(reg);
+ val &= ~BIT(0);
+ writel_relaxed(val, reg);
+}
+
+static int pil_lpass_shutdown(struct pil_desc *pil)
+{
+ struct q6v5_data *drv = dev_get_drvdata(pil->dev);
+ int ret;
+ u32 status;
+
+ writel_relaxed(1, drv->axi_halt_base + AXI_HALTREQ);
+ ret = readl_poll_timeout(drv->axi_halt_base + AXI_HALTACK,
+ status, status, 50, HALT_ACK_TIMEOUT_US);
+ if (ret)
+ dev_err(pil->dev, "Port halt timeout\n");
+ else if (!readl_relaxed(drv->axi_halt_base + AXI_IDLE))
+ dev_err(pil->dev, "Port halt failed\n");
+ writel_relaxed(0, drv->axi_halt_base + AXI_HALTREQ);
+
+ /* Make sure Q6 registers are accessible */
+ writel_relaxed(0, drv->clk_base + LPASS_Q6SS_BCR);
+ clk_reg_enable(drv->clk_base + LPASS_Q6SS_AHB_LFABIF_CBCR);
+ mb();
+
+ pil_q6v5_shutdown(pil);
+
+ /* Disable clocks and assert subsystem resets. */
+ clk_reg_disable(drv->clk_base + LPASS_Q6SS_AHB_LFABIF_CBCR);
+ clk_reg_disable(drv->clk_base + LPASS_Q6SS_XO_CBCR);
+ writel_relaxed(1, drv->clk_base + LPASS_Q6SS_BCR);
+
+ return 0;
+}
+
+static int pil_lpass_reset(struct pil_desc *pil)
+{
+ struct q6v5_data *drv = dev_get_drvdata(pil->dev);
+
+ /*
+ * Bring subsystem out of reset and enable required
+ * regulators and clocks.
+ */
+ writel_relaxed(0, drv->clk_base + LPASS_Q6SS_BCR);
+ clk_reg_enable(drv->clk_base + LPASS_Q6SS_XO_CBCR);
+ clk_reg_enable(drv->clk_base + LPASS_Q6SS_AHB_LFABIF_CBCR);
+ mb();
+
+ /* Program Image Address */
+ writel_relaxed(((drv->start_addr >> 4) & 0x0FFFFFF0),
+ drv->reg_base + QDSP6SS_RST_EVB);
+
+ return pil_q6v5_reset(pil);
+}
+
+static struct pil_reset_ops pil_lpass_ops = {
+ .init_image = pil_q6v5_init_image,
+ .proxy_vote = pil_q6v5_make_proxy_votes,
+ .proxy_unvote = pil_q6v5_remove_proxy_votes,
+ .auth_and_reset = pil_lpass_reset,
+ .shutdown = pil_lpass_shutdown,
+};
+
+static int __devinit pil_lpass_driver_probe(struct platform_device *pdev)
+{
+ struct q6v5_data *drv;
+ struct pil_desc *desc;
+ struct resource *res;
+
+ desc = pil_q6v5_init(pdev);
+ drv = platform_get_drvdata(pdev);
+
+ desc->ops = &pil_lpass_ops;
+ desc->owner = THIS_MODULE;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ drv->axi_halt_base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!drv->axi_halt_base)
+ return -ENOMEM;
+
+ drv->pil = msm_pil_register(desc);
+ if (IS_ERR(drv->pil))
+ return PTR_ERR(drv->pil);
+
+ return 0;
+}
+
+static int __devexit pil_lpass_driver_exit(struct platform_device *pdev)
+{
+ struct q6v5_data *drv = platform_get_drvdata(pdev);
+ msm_pil_unregister(drv->pil);
+ return 0;
+}
+
+static struct of_device_id lpass_match_table[] = {
+ { .compatible = "qcom,pil-q6v5-lpass" },
+ {}
+};
+
+static struct platform_driver pil_lpass_driver = {
+ .probe = pil_lpass_driver_probe,
+ .remove = __devexit_p(pil_lpass_driver_exit),
+ .driver = {
+ .name = "pil-q6v5-lpass",
+ .of_match_table = lpass_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pil_lpass_init(void)
+{
+ return platform_driver_register(&pil_lpass_driver);
+}
+module_init(pil_lpass_init);
+
+static void __exit pil_lpass_exit(void)
+{
+ platform_driver_unregister(&pil_lpass_driver);
+}
+module_exit(pil_lpass_exit);
+
+MODULE_DESCRIPTION("Support for booting low-power audio subsystems with QDSP6v5 (Hexagon) processors");
+MODULE_LICENSE("GPL v2");
diff --git a/arch/arm/mach-msm/pil-q6v5.h b/arch/arm/mach-msm/pil-q6v5.h
index 23471d1..b17d4e7 100644
--- a/arch/arm/mach-msm/pil-q6v5.h
+++ b/arch/arm/mach-msm/pil-q6v5.h
@@ -22,6 +22,7 @@
struct q6v5_data {
void __iomem *reg_base;
void __iomem *clk_base;
+ void __iomem *axi_halt_base;
void __iomem *rmb_base;
unsigned long start_addr;
struct regulator *vreg;