msm: pil-q6v5-mss: Add PIL support for copper's MSS subsystem
The pil-q6v5-mss driver is used to bring Modem Subsystems (MSS)
containing QDSP6v5 processors out of reset and start their execution
of code. It also supports shutting down these subsystems 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 MSS (ex. copper's LPASS).
Change-Id: I86640062d48bc080d51ed7def56d45e0c0391f33
Signed-off-by: Matt Wagantall <mattw@codeaurora.org>
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index a24498c..a6da656 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -1857,8 +1857,15 @@
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.
+ Support for booting and shutting down QDSP6v5 (Hexagon) processors
+ in low power audio subsystems.
+
+config MSM_PIL_MSS_QDSP6V5
+ tristate "MSS QDSP6v5 (Hexagon) Boot Support"
+ depends on MSM_PIL
+ help
+ Support for booting and shutting down QDSP6v5 (Hexagon) processors
+ in modem subsystems.
config MSM_PIL_RIVA
tristate "RIVA (WCNSS) Boot Support"
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index bcc6582..5a9bab7 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -68,6 +68,7 @@
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_MSS_QDSP6V5) += pil-q6v5.o pil-q6v5-mss.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 62bab86..0e320fc 100644
--- a/arch/arm/mach-msm/board-copper.c
+++ b/arch/arm/mach-msm/board-copper.c
@@ -495,6 +495,7 @@
"msm_sdcc.4", NULL),
OF_DEV_AUXDATA("qcom,pil-q6v5-lpass", 0xFE200000, \
"pil-q6v5-lpass", NULL),
+ OF_DEV_AUXDATA("qcom,pil-q6v5-mss", 0xFC880000, "pil-q6v5-mss", NULL),
OF_DEV_AUXDATA("qcom,pil-pronto", 0xFB21B000, \
"pil_pronto", NULL),
OF_DEV_AUXDATA("qcom,msm-rng", 0xF9BFF000, \
diff --git a/arch/arm/mach-msm/clock-copper.c b/arch/arm/mach-msm/clock-copper.c
index fc0b0af..99a6924 100644
--- a/arch/arm/mach-msm/clock-copper.c
+++ b/arch/arm/mach-msm/clock-copper.c
@@ -4577,6 +4577,7 @@
static struct clk_lookup msm_clocks_copper[] = {
CLK_LOOKUP("xo", cxo_clk_src.c, "msm_otg"),
CLK_LOOKUP("xo", cxo_clk_src.c, "pil-q6v5-lpass"),
+ CLK_LOOKUP("xo", cxo_clk_src.c, "pil-q6v5-mss"),
CLK_LOOKUP("xo", cxo_clk_src.c, "pil_pronto"),
CLK_LOOKUP("measure", measure_clk.c, "debug"),
@@ -4773,12 +4774,12 @@
CLK_LOOKUP("ebit_clk", audio_core_lpaif_pcm1_ebit_clk.c, ""),
CLK_LOOKUP("ibit_clk", audio_core_lpaif_pcm1_ibit_clk.c, ""),
- CLK_LOOKUP("core_clk", mss_xo_q6_clk.c, ""),
- CLK_LOOKUP("bus_clk", mss_bus_q6_clk.c, ""),
+ CLK_LOOKUP("core_clk", mss_xo_q6_clk.c, "pil-q6v5-mss"),
+ CLK_LOOKUP("bus_clk", mss_bus_q6_clk.c, "pil-q6v5-mss"),
+ CLK_LOOKUP("bus_clk", gcc_mss_cfg_ahb_clk.c, ""),
+ CLK_LOOKUP("mem_clk", gcc_boot_rom_ahb_clk.c, "pil-q6v5-mss"),
CLK_LOOKUP("core_clk", q6ss_xo_clk.c, "pil-q6v5-lpass"),
CLK_LOOKUP("bus_clk", q6ss_ahb_lfabif_clk.c, "pil-q6v5-lpass"),
- CLK_LOOKUP("mem_clk", gcc_boot_rom_ahb_clk.c, ""),
- CLK_LOOKUP("bus_clk", gcc_mss_cfg_ahb_clk.c, ""),
CLK_LOOKUP("core_clk", gcc_prng_ahb_clk.c, "msm_rng"),
/* TODO: Remove dummy clocks as soon as they become unnecessary */
diff --git a/arch/arm/mach-msm/pil-q6v5-mss.c b/arch/arm/mach-msm/pil-q6v5-mss.c
new file mode 100644
index 0000000..e279f99
--- /dev/null
+++ b/arch/arm/mach-msm/pil-q6v5-mss.c
@@ -0,0 +1,291 @@
+/*
+ * 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/ioport.h>
+#include <linux/elf.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/clk.h>
+
+#include "peripheral-loader.h"
+#include "pil-q6v5.h"
+
+/* Q6 Register Offsets */
+#define QDSP6SS_RST_EVB 0x010
+
+/* AXI Halting Registers */
+#define MSS_Q6_HALT_BASE 0x180
+#define MSS_MODEM_HALT_BASE 0x200
+#define MSS_NC_HALT_BASE 0x280
+
+/* RMB Status Register Values */
+#define STATUS_PBL_SUCCESS 0x1
+#define STATUS_XPU_UNLOCKED 0x1
+#define STATUS_XPU_UNLOCKED_SCRIBBLED 0x2
+
+/* PBL/MBA interface registers */
+#define RMB_MBA_IMAGE 0x00
+#define RMB_PBL_STATUS 0x04
+#define RMB_MBA_STATUS 0x0C
+
+#define PBL_MBA_WAIT_TIMEOUT_US 100000
+#define PROXY_TIMEOUT_MS 10000
+#define POLL_INTERVAL_US 50
+
+static int pil_mss_power_up(struct device *dev)
+{
+ int ret;
+ struct q6v5_data *drv = dev_get_drvdata(dev);
+
+ ret = regulator_enable(drv->vreg);
+ if (ret)
+ dev_err(dev, "Failed to enable regulator.\n");
+
+ return ret;
+}
+
+static int pil_mss_power_down(struct device *dev)
+{
+ struct q6v5_data *drv = dev_get_drvdata(dev);
+
+ return regulator_disable(drv->vreg);
+}
+
+static int wait_for_mba_ready(struct device *dev)
+{
+ struct q6v5_data *drv = dev_get_drvdata(dev);
+ int ret;
+ u32 status;
+
+ /* Wait for PBL completion. */
+ ret = readl_poll_timeout(drv->rmb_base + RMB_PBL_STATUS, status,
+ status != 0, POLL_INTERVAL_US, PBL_MBA_WAIT_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "PBL boot timed out\n");
+ return ret;
+ }
+ if (status != STATUS_PBL_SUCCESS) {
+ dev_err(dev, "PBL returned unexpected status %d\n", status);
+ return -EINVAL;
+ }
+
+ /* Wait for MBA completion. */
+ ret = readl_poll_timeout(drv->rmb_base + RMB_MBA_STATUS, status,
+ status != 0, POLL_INTERVAL_US, PBL_MBA_WAIT_TIMEOUT_US);
+ if (ret) {
+ dev_err(dev, "MBA boot timed out\n");
+ return ret;
+ }
+ if (status != STATUS_XPU_UNLOCKED &&
+ status != STATUS_XPU_UNLOCKED_SCRIBBLED) {
+ dev_err(dev, "MBA returned unexpected status %d\n", status);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pil_mss_shutdown(struct pil_desc *pil)
+{
+ struct q6v5_data *drv = dev_get_drvdata(pil->dev);
+
+ pil_q6v5_halt_axi_port(pil, drv->axi_halt_base + MSS_Q6_HALT_BASE);
+ pil_q6v5_halt_axi_port(pil, drv->axi_halt_base + MSS_MODEM_HALT_BASE);
+ pil_q6v5_halt_axi_port(pil, drv->axi_halt_base + MSS_NC_HALT_BASE);
+
+ /*
+ * If the shutdown function is called before the reset function, clocks
+ * and power will not be enabled yet. Enable them here so that register
+ * writes performed during the shutdown succeed.
+ */
+ if (drv->is_booted == false) {
+ pil_mss_power_up(pil->dev);
+ pil_q6v5_enable_clks(pil);
+ }
+ pil_q6v5_shutdown(pil);
+
+ pil_q6v5_disable_clks(pil);
+ pil_mss_power_down(pil->dev);
+
+ writel_relaxed(1, drv->restart_reg);
+
+ drv->is_booted = false;
+
+ return 0;
+}
+
+static int pil_mss_reset(struct pil_desc *pil)
+{
+ struct q6v5_data *drv = dev_get_drvdata(pil->dev);
+ int ret;
+
+ writel_relaxed(0, drv->restart_reg);
+ mb();
+
+ /*
+ * Bring subsystem out of reset and enable required
+ * regulators and clocks.
+ */
+ ret = pil_mss_power_up(pil->dev);
+ if (ret)
+ goto err_power;
+
+ ret = pil_q6v5_enable_clks(pil);
+ if (ret)
+ goto err_clks;
+
+ /* Program Image Address */
+ if (drv->self_auth)
+ writel_relaxed(drv->start_addr, drv->rmb_base + RMB_MBA_IMAGE);
+ else
+ writel_relaxed((drv->start_addr >> 4) & 0x0FFFFFF0,
+ drv->reg_base + QDSP6SS_RST_EVB);
+
+ ret = pil_q6v5_reset(pil);
+ if (ret)
+ goto err_q6v5_reset;
+
+ /* Wait for MBA to start. Check for PBL and MBA errors while waiting. */
+ if (drv->self_auth) {
+ ret = wait_for_mba_ready(pil->dev);
+ if (ret)
+ goto err_auth;
+ }
+
+ drv->is_booted = true;
+
+ return 0;
+
+err_auth:
+ pil_q6v5_shutdown(pil);
+err_q6v5_reset:
+ pil_q6v5_disable_clks(pil);
+err_clks:
+ pil_mss_power_down(pil->dev);
+err_power:
+ return ret;
+}
+
+static struct pil_reset_ops pil_mss_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_mss_reset,
+ .shutdown = pil_mss_shutdown,
+};
+
+static int __devinit pil_mss_driver_probe(struct platform_device *pdev)
+{
+ struct q6v5_data *drv;
+ struct pil_desc *desc;
+ struct resource *res;
+ int ret;
+
+ desc = pil_q6v5_init(pdev);
+ if (IS_ERR(desc))
+ return PTR_ERR(desc);
+ drv = platform_get_drvdata(pdev);
+ if (drv == NULL)
+ return -ENODEV;
+
+ desc->ops = &pil_mss_ops;
+ desc->owner = THIS_MODULE;
+ desc->proxy_timeout = PROXY_TIMEOUT_MS;
+
+ of_property_read_u32(pdev->dev.of_node, "qcom,pil-self-auth",
+ &drv->self_auth);
+ if (drv->self_auth) {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ drv->rmb_base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!drv->rmb_base)
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ drv->restart_reg = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!drv->restart_reg)
+ return -ENOMEM;
+
+ drv->vreg = devm_regulator_get(&pdev->dev, "vdd_mss");
+ if (IS_ERR(drv->vreg))
+ return PTR_ERR(drv->vreg);
+
+ ret = regulator_set_voltage(drv->vreg, 1150000, 1150000);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to set regulator's voltage.\n");
+
+ ret = regulator_set_optimum_mode(drv->vreg, 100000);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to set regulator's mode.\n");
+ return ret;
+ }
+
+ drv->mem_clk = devm_clk_get(&pdev->dev, "mem_clk");
+ if (IS_ERR(drv->mem_clk))
+ return PTR_ERR(drv->mem_clk);
+
+ drv->pil = msm_pil_register(desc);
+ if (IS_ERR(drv->pil))
+ return PTR_ERR(drv->pil);
+
+ return 0;
+}
+
+static int __devexit pil_mss_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 mss_match_table[] = {
+ { .compatible = "qcom,pil-q6v5-mss" },
+ {}
+};
+
+static struct platform_driver pil_mss_driver = {
+ .probe = pil_mss_driver_probe,
+ .remove = __devexit_p(pil_mss_driver_exit),
+ .driver = {
+ .name = "pil-q6v5-mss",
+ .of_match_table = mss_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pil_mss_init(void)
+{
+ return platform_driver_register(&pil_mss_driver);
+}
+module_init(pil_mss_init);
+
+static void __exit pil_mss_exit(void)
+{
+ platform_driver_unregister(&pil_mss_driver);
+}
+module_exit(pil_mss_exit);
+
+MODULE_DESCRIPTION("Support for booting modem subsystems with QDSP6v5 Hexagon processors");
+MODULE_LICENSE("GPL v2");
diff --git a/arch/arm/mach-msm/pil-q6v5.c b/arch/arm/mach-msm/pil-q6v5.c
index 6a96990..a362a7e3 100644
--- a/arch/arm/mach-msm/pil-q6v5.c
+++ b/arch/arm/mach-msm/pil-q6v5.c
@@ -121,9 +121,16 @@
ret = clk_prepare_enable(drv->bus_clk);
if (ret)
goto err_bus_clk;
+ if (drv->mem_clk) {
+ ret = clk_prepare_enable(drv->mem_clk);
+ if (ret)
+ goto err_mem_clk;
+ }
return 0;
+err_mem_clk:
+ clk_disable_unprepare(drv->bus_clk);
err_bus_clk:
clk_disable_unprepare(drv->core_clk);
err_core_clk:
@@ -139,6 +146,7 @@
clk_disable_unprepare(drv->bus_clk);
clk_disable_unprepare(drv->core_clk);
+ clk_disable_unprepare(drv->mem_clk);
clk_reset(drv->core_clk, CLK_RESET_ASSERT);
}
EXPORT_SYMBOL(pil_q6v5_disable_clks);
diff --git a/arch/arm/mach-msm/pil-q6v5.h b/arch/arm/mach-msm/pil-q6v5.h
index a9a8d07..e0d7a20 100644
--- a/arch/arm/mach-msm/pil-q6v5.h
+++ b/arch/arm/mach-msm/pil-q6v5.h
@@ -24,8 +24,10 @@
struct clk *xo;
struct clk *bus_clk;
struct clk *core_clk;
+ struct clk *mem_clk;
void __iomem *axi_halt_base;
void __iomem *rmb_base;
+ void __iomem *restart_reg;
unsigned long start_addr;
struct regulator *vreg;
bool is_booted;