summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorArun KS <arunks@codeaurora.org>2015-10-30 15:33:47 +0530
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-23 21:12:02 -0700
commit1e492a56bc4cff5a9476a57d475018b735942df1 (patch)
treeeb16041f22f8197ca7de315a84faf2e806ce11dc /drivers
parent4bf1c7f330f0e7e507b942b4a1998c8e8533caae (diff)
soc: qcom: Add in-rush current mitigation driver
On few recent targets APSS L2 memory is moved to APC domain which were earlier on Mx domain. This can cause inrush current while bringing up huge memories like modem and adsp. To mitigate inrush current, bring up comparatively lesser memory in size(for eg MDP memory) before bringing up huge memories like modem or adsp. This way MDP memory introduce an intermediate load on MX rail. During boot, gdsc driver will set MEM and PERIPHERAL bits. This driver makes sure that dependent subsystems are powered up. Once done, call gdsc_allow_clear_retention() API to allow retention of MDP memories. Change-Id: I54011eb1b6cc38b2c33a67b8b9cc5eaadbd42c6a Signed-off-by: Arun KS <arunks@codeaurora.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/soc/qcom/Kconfig8
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/inrush-current-mitigation.c168
3 files changed, 177 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 39cb4b96deb7..abbcba0e9168 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -1,6 +1,14 @@
#
# QCOM Soc drivers
#
+config MSM_INRUSH_CURRENT_MITIGATION
+ bool "Inrush-current mitigation Driver"
+ help
+ This driver helps in mitigating in-rush current on MSM
+ chipsets which has voltage droop issues due to sudden
+ huge load on a rail. This driver introduces an intermediate
+ load to mitigate the in-rush current.
+
config MSM_SMEM
depends on ARCH_QCOM
depends on REMOTE_SPINLOCK_MSM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index d90a929bc95f..9c3787035eda 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,5 +1,6 @@
KASAN_SANITIZE_scm.o := n
+obj-$(CONFIG_MSM_INRUSH_CURRENT_MITIGATION) += inrush-current-mitigation.o
obj-$(CONFIG_MSM_SMEM) += msm_smem.o smem_debug.o
obj-$(CONFIG_MSM_SMD) += msm_smd.o smd_debug.o smd_private.o smd_init_dt.o smsm_debug.o
obj-$(CONFIG_MSM_GLINK) += glink.o glink_debugfs.o glink_ssr.o
diff --git a/drivers/soc/qcom/inrush-current-mitigation.c b/drivers/soc/qcom/inrush-current-mitigation.c
new file mode 100644
index 000000000000..9ffa4779a9c6
--- /dev/null
+++ b/drivers/soc/qcom/inrush-current-mitigation.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2015, 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/slab.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/async.h>
+#include <linux/clk/gdsc.h>
+#include <linux/regulator/consumer.h>
+#include <soc/qcom/subsystem_notif.h>
+
+struct subsystem {
+ const char *name;
+ void *notif_handle;
+ struct notifier_block nb;
+ bool booted;
+ struct inrush_driver_data *drv_data;
+};
+
+struct inrush_driver_data {
+ int subsys_count;
+ int subsys_boot_count;
+ struct regulator *vreg;
+ /* Must be the last member */
+ struct subsystem *subsystems;
+};
+
+#define notifier_to_subsystem(d) container_of(d, struct subsystem, nb)
+
+static void free_resources(void *data, async_cookie_t cookie)
+{
+ struct inrush_driver_data *drv_data = data;
+ struct subsystem *subsys;
+ int i;
+
+ gdsc_allow_clear_retention(drv_data->vreg);
+ devm_regulator_put(drv_data->vreg);
+
+ for (i = 0; i < drv_data->subsys_count; i++) {
+ subsys = &drv_data->subsystems[i];
+ subsys_notif_unregister_notifier(subsys->notif_handle,
+ &subsys->nb);
+ }
+
+ kfree(drv_data);
+ pr_info("inrush-current-mitigation driver exited\n");
+}
+
+static int mitigate_inrush_notifier_cb(struct notifier_block *nb,
+ unsigned long code, void *ss_handle)
+{
+ struct subsystem *subsys = notifier_to_subsystem(nb);
+ struct inrush_driver_data *drv_data = subsys->drv_data;
+
+ if (subsys->booted)
+ return NOTIFY_DONE;
+
+ switch (code) {
+ case SUBSYS_AFTER_POWERUP:
+ pr_info("%s: subsystem %s has completed powerup\n", __func__,
+ subsys->name);
+ subsys->booted = true;
+ drv_data->subsys_boot_count++;
+ break;
+ }
+
+ /*
+ * If all subsystems are up, job of this driver ends, lets
+ * free resources.
+ */
+ if (drv_data->subsys_count == drv_data->subsys_boot_count)
+ async_schedule(free_resources, drv_data);
+
+ return NOTIFY_DONE;
+}
+
+static int mitigate_inrush_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int i, retval;
+ struct subsystem *subsys;
+ struct inrush_driver_data *drv_data;
+
+ retval = of_property_count_strings(np,
+ "qcom,dependent-subsystems");
+ if (IS_ERR_VALUE(retval)) {
+ dev_err(dev, "Failed to get dependent subsystems\n");
+ return -EINVAL;
+ }
+
+ drv_data = kzalloc((retval * sizeof(struct subsystem) +
+ sizeof(struct inrush_driver_data)), GFP_KERNEL);
+
+ if (!drv_data)
+ return -ENOMEM;
+
+ drv_data->subsystems = (void *)drv_data +
+ sizeof(struct inrush_driver_data);
+ drv_data->subsys_count = retval;
+
+ for (i = 0; i < drv_data->subsys_count; i++) {
+ subsys = &drv_data->subsystems[i];
+ subsys->drv_data = drv_data;
+ of_property_read_string_index(np, "qcom,dependent-subsystems",
+ i, &subsys->name);
+ subsys->nb.notifier_call = mitigate_inrush_notifier_cb;
+ subsys->notif_handle =
+ subsys_notif_register_notifier(subsys->name,
+ &subsys->nb);
+ if (IS_ERR(subsys->notif_handle)) {
+ dev_err(dev, "Notifier registration failed for %s\n",
+ subsys->name);
+ retval = PTR_ERR(subsys->notif_handle);
+ goto err_subsys_notif;
+ }
+ }
+
+ drv_data->vreg = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(drv_data->vreg)) {
+ dev_err(dev, "Failed to get regulator\n");
+ return PTR_ERR(drv_data->vreg);
+ }
+
+ return 0;
+
+err_subsys_notif:
+ for (i = 0; i < drv_data->subsys_count; i++) {
+ subsys = &drv_data->subsystems[i];
+ subsys_notif_unregister_notifier(subsys->notif_handle,
+ &subsys->nb);
+ }
+ kfree(drv_data);
+ return retval;
+}
+
+static const struct of_device_id mitigate_inrush_match_table[] = {
+ { .compatible = "qcom,msm-inrush-current-mitigation" },
+ {},
+};
+
+static struct platform_driver mitigate_inrush_driver = {
+ .probe = mitigate_inrush_probe,
+ .driver = {
+ .name = "msm-inrush-current",
+ .owner = THIS_MODULE,
+ .of_match_table = mitigate_inrush_match_table,
+ },
+};
+
+static int init_msm_mitigate_inrush(void)
+{
+ return platform_driver_register(&mitigate_inrush_driver);
+}
+late_initcall(init_msm_mitigate_inrush);