Merge "msm: pil-q6v5-lpass: Add PIL support for copper's LPASS subsystem" into msm-3.0
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;