Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a84f8fd9 authored by Arun KS's avatar Arun KS
Browse files

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: default avatarArun KS <arunks@codeaurora.org>
parent e5cb9812
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
In Rush Current Mitigation driver:

On recent targets, APSS L2 memories are moved to APC domain which were
earlier on Mx domain. Analysis suggests that on current targets APSS L2
memories provide reverse capacitance on Mx and this used to provide buffer
while powering ON Q6 L2. Now due to L2s moving to APC, the cushion is not
available. So there is a chance of droop when Q6 L2 memories are being
powered up.

During Q6 low power modes L2 is kept in retention only and never collapsed. So
the cases where it needs to be taken care is during PIL(modem and adsp bringup
during boot). So in cold boot path before bringing up modem or adsp, turn on MM
memories. Hence providing a intermediate load.

Required properties:
- compatible:			Must be qcom,msm-inrush-current-mitigation
- qcom,dependent-subsystems:	List of subsystems which need the intermediate load
- vdd-supply:			gdsc handle to switch on memory.

Example:
	qcom,inrush-current {
		compatible = "qcom,msm-inrush-current-mitigation";
		qcom,dependent-subsystems = "modem", "adsp";
		vdd-supply = <&gdsc_mdss>;
	};
+8 −0
Original line number Diff line number Diff line
@@ -22,6 +22,14 @@ config CP_ACCESS64

          If unsure, say N.

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_QDSP6_APRV2
        bool "Audio QDSP6 APRv2 support"
        depends on MSM_SMD
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)

ccflags-$(CONFIG_MSM_QBT1000) += -Idrivers/misc/

obj-$(CONFIG_MSM_INRUSH_CURRENT_MITIGATION) += inrush-current-mitigation.o
obj-$(CONFIG_MSM_GLADIATOR_ERP) += gladiator_erp.o
obj-$(CONFIG_ARM64) += idle-v8.o cpu_ops.o
obj-$(CONFIG_MSM_BOOT_STATS) += boot_stats.o
+168 −0
Original line number Diff line number Diff line
/*
 * 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);