// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2025, Qualcomm Innovation Center, Inc. All rights reserved.
 */

#define pr_fmt(fmt) "%s: %s: " fmt, KBUILD_MODNAME, __func__

#include <linux/arm-smccc.h>
#include <linux/cdev.h>
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeup.h>
#include <linux/remoteproc.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <linux/suspend.h>
#include <linux/syscore_ops.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>
#include <soc/qcom/rpm-smd.h>
#include <soc/qcom/subsystem_sleep_stats.h>
#include <linux/soc/qcom/qcom_aoss.h>

#include "linux/power_state.h"

#if IS_ENABLED(CONFIG_ARCH_MONACO)
#define DS_ENTRY_SMC_ID		0xC3000924
#else
#define DS_ENTRY_SMC_ID		0xC3000923
#endif

#if IS_ENABLED(CONFIG_MSM_RPM_SMD)
#define RPM_XO_DS_REQ		0x73646f78
#define RPM_XO_DS_ID		0x0
#define RPM_XO_DS_KEY		0x62616e45
#define RPM_XO_DS_ENTER_VALUE		0x0
#define RPM_XO_DS_EXIT_VALUE		0x1
#endif

#define DS_NUM_PARAMETERS	1
#define DS_ENTRY		1
#define DS_EXIT			0

#define POWER_STATS_BASEMINOR		0
#define POWER_STATS_MAX_MINOR		1
#define POWER_STATE_DEVICE_NAME		"power_state"
#define STRING_LEN			32
#define PS_PM_NOTIFIER_PRIORITY		100
#define PS_SSR_NOTIFIER_PRIORITY	0

#define MAX_QMP_MSG_SIZE	96
#define SUSBSYSTEM_SLEEP_CHECK_MAX_RETRIES	2
#define DELAY	50

enum power_states {
	ACTIVE,
	DEEPSLEEP,
	HIBERNATE,
};

static char * const power_state[] = {
	[ACTIVE] = "active",
	[DEEPSLEEP] = "deepsleep",
	[HIBERNATE] = "hibernate",
};

struct subsystem_event_data {
	const char *name;
	enum ps_event_type enter;
	enum ps_event_type exit;
};

static struct subsystem_event_data event_data[] = {
	{ "mpss", MDSP_BEFORE_POWERDOWN, MDSP_AFTER_POWERUP },
	{ "lpass", ADSP_BEFORE_POWERDOWN, ADSP_AFTER_POWERUP },
	{ "cdsp", CDSP_BEFORE_POWERDOWN, CDSP_AFTER_POWERUP },
	{ "cdsp1", CDSP1_BEFORE_POWERDOWN, CDSP1_AFTER_POWERUP },
	{ "gpdsp0", GPDSP0_BEFORE_POWERDOWN, GPDSP0_AFTER_POWERUP },
	{ "gpdsp1", GPDSP1_BEFORE_POWERDOWN, GPDSP1_AFTER_POWERUP },
};

struct subsystem_data {
	struct list_head list;
	const char *name;
	bool ignore_ssr;
	enum ps_event_type enter;
	enum ps_event_type exit;
	phandle rproc_handle;
	void *ssr_handle;
	struct notifier_block ps_ssr_nb;
};

struct power_state_drvdata {
	struct class *ps_class;
	struct device *ps_dev;
	struct cdev ps_cdev;
	dev_t ps_dev_no;
	struct kobject *ps_kobj;
	struct kobj_attribute ps_ka;
	struct kobj_attribute ds_ka;
	struct kobj_attribute ds_allow_ka;
	struct wakeup_source *ps_ws;
	struct notifier_block ps_pm_nb;
	struct qmp *qmp;
	struct msm_rpm_kvp kvp_req;
	struct syscore_ops ps_ops;
	enum power_states current_state;
	int subsys_count;
	struct list_head sub_sys_list;
	int deep_sleep_cnt;
	int deep_sleep_allowed;
};

static struct power_state_drvdata *drv;

static int subsys_suspend(struct subsystem_data *ss_data, struct rproc *rproc, uint32_t state)
{
	int ret = 0;

	switch (state) {
	case SUBSYS_DEEPSLEEP:
	case SUBSYS_HIBERNATE:
		ss_data->ignore_ssr = true;
#if IS_ENABLED(CONFIG_QCOM_DS_SKIP_Q6_STOP)
		adsp_set_ops_stop(rproc, true);
#endif
		rproc_shutdown(rproc);
		ss_data->ignore_ssr = false;
		break;
	default:
		pr_err("Invalid %s suspend state\n", ss_data->name);
		ret = -1;
		break;
	}

	return ret;
}

static int subsys_resume(struct subsystem_data *ss_data, struct rproc *rproc, u32 state)
{
	int ret = 0;

	switch (state) {
	case SUBSYS_DEEPSLEEP:
	case SUBSYS_HIBERNATE:
		ss_data->ignore_ssr = true;
#if IS_ENABLED(CONFIG_QCOM_DS_SKIP_Q6_STOP)
		adsp_set_ops_stop(rproc, false);
#endif
		ret = rproc_boot(rproc);
		ss_data->ignore_ssr = false;
		break;
	default:
		pr_err("Invalid %s resume state\n", ss_data->name);
		ret = -1;
		break;
	}

	return ret;
}

static int subsystem_resume(struct power_state_drvdata *drv, u32 state)
{
	struct subsystem_data *ss_data;
	int ret = 0;
	struct rproc *rproc = NULL;

	list_for_each_entry(ss_data, &drv->sub_sys_list, list) {
		pr_info("%s subsystem resume start\n", ss_data->name);
		rproc = rproc_get_by_phandle(ss_data->rproc_handle);
		if (!rproc)
			return -ENODEV;

		ret = subsys_resume(ss_data, rproc, state);
		if (ret) {
			pr_err("%s subsystem resume failed\n", ss_data->name);
			BUG();
		}
		rproc_put(rproc);
		pr_info("%s subsystem resume complete\n", ss_data->name);
	}

	return ret;
}

static int subsystem_suspend(struct power_state_drvdata *drv, u32 state)
{
	struct subsystem_data *ss_data;
	int ret = 0;
	struct rproc *rproc = NULL;
#if IS_ENABLED(CONFIG_QCOM_DS_SKIP_Q6_STOP)
	bool subsys_sleep_status;
	int i;

	for (i = 0; i < SUSBSYSTEM_SLEEP_CHECK_MAX_RETRIES; i++) {
		subsys_sleep_status = current_subsystem_sleep();
		if (subsys_sleep_status)
			break;
		pr_err("subsytems are not in sleep retrying.....\n");
		msleep(DELAY);
	}

	if (i == SUSBSYSTEM_SLEEP_CHECK_MAX_RETRIES) {
		pr_err("Error: still subsystems are not in sleep\n");
		BUG();
	}
#endif

	list_for_each_entry(ss_data, &drv->sub_sys_list, list) {
		pr_info("%s subsystem suspend start\n", ss_data->name);
		rproc = rproc_get_by_phandle(ss_data->rproc_handle);
		if (!rproc)
			return -ENODEV;

		ret = subsys_suspend(ss_data, rproc, state);
		if (ret) {
			pr_err("%s subsystem suspend failed\n", ss_data->name);
			BUG();
		}
		rproc_put(rproc);
		pr_info("%s subsystem suspend complete\n", ss_data->name);
	}

	return ret;
}

static int ps_open(struct inode *inode, struct file *file)
{
	struct power_state_drvdata *drv = NULL;

	if (!inode || !inode->i_cdev || !file)
		return -EINVAL;

	drv = container_of(inode->i_cdev, struct power_state_drvdata, ps_cdev);
	file->private_data = drv;

	return 0;
}

#if IS_ENABLED(CONFIG_MSM_RPM_SMD)
static int send_deep_sleep_vote(int state, struct power_state_drvdata *drv)
{
	u32 val;

	if (state == DS_ENTRY)
		val = RPM_XO_DS_ENTER_VALUE;
	else
		val = RPM_XO_DS_EXIT_VALUE;

	drv->kvp_req.key = RPM_XO_DS_KEY;
	drv->kvp_req.data = (void *)&val;
	drv->kvp_req.length = sizeof(val);

	return msm_rpm_send_message(MSM_RPM_CTX_SLEEP_SET, RPM_XO_DS_REQ,
				    RPM_XO_DS_ID, &drv->kvp_req, 1);
}
#elif IS_ENABLED(CONFIG_NOTIFY_AOP)
static int send_deep_sleep_vote(int state, struct power_state_drvdata *drv)
{
	char buf[MAX_QMP_MSG_SIZE] = {};

	if (state == DS_ENTRY)
		scnprintf(buf, sizeof(buf), "{class: deep_sleep, res: 1}");
	else
		scnprintf(buf, sizeof(buf), "{class: deep_sleep, res: 0}");

	return qmp_send(drv->qmp, buf, sizeof(buf));
}
#else
static int send_deep_sleep_vote(int state, struct power_state_drvdata *drv)
{
	return 0;
}
#endif

static long ps_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct power_state_drvdata *drv = file->private_data;
	int ret = 0;

	switch (cmd) {
	case LPM_ACTIVE:
	case POWER_STATE_LPM_ACTIVE:
		pr_debug("State changed to Active\n");
		if (pm_suspend_via_firmware()) {
			pm_suspend_clear_flags();
			__pm_relax(drv->ps_ws);
		}
		drv->current_state = ACTIVE;
		pr_info("low power mode exit complete\n");
		break;

	case ENTER_DEEPSLEEP:
	case POWER_STATE_ENTER_DEEPSLEEP:
		pr_debug("Enter Deep Sleep\n");
		ret = subsystem_suspend(drv, SUBSYS_DEEPSLEEP);
		drv->current_state = DEEPSLEEP;
		break;

	case ENTER_HIBERNATE:
	case POWER_STATE_ENTER_HIBERNATE:
		pr_debug("Enter Hibernate\n");
		ret = subsystem_suspend(drv, SUBSYS_HIBERNATE);
		drv->current_state = HIBERNATE;
		break;

	case EXIT_DEEPSLEEP_STATE:
	case POWER_STATE_EXIT_DEEPSLEEP_STATE:
		pr_debug("Exit Deep Sleep\n");
		ret = subsystem_resume(drv, SUBSYS_DEEPSLEEP);
		break;

	case EXIT_HIBERNATE_STATE:
	case POWER_STATE_EXIT_HIBERNATE_STATE:
		pr_debug("Exit Hibernate\n");
		ret = subsystem_resume(drv, SUBSYS_HIBERNATE);
		break;

	case MODEM_SUSPEND:
	case MODEM_EXIT:
	case POWER_STATE_MODEM_SUSPEND:
	case POWER_STATE_MODEM_EXIT:
	case ADSP_SUSPEND:
	case ADSP_EXIT:
	case POWER_STATE_ADSP_SUSPEND:
	case POWER_STATE_ADSP_EXIT:
		pr_debug("Deprecated ioctl\n");
		break;

	default:
		ret = -ENOIOCTLCMD;
		pr_err("%s: Default\n", __func__);
		break;
	}

	return ret;
}

static long compat_ps_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned int nr = _IOC_NR(cmd);

	return (long)ps_ioctl(file, nr, arg);
}

static const struct file_operations ps_fops = {
	.owner = THIS_MODULE,
	.open = ps_open,
	.unlocked_ioctl = ps_ioctl,
	.compat_ioctl = compat_ps_ioctl,
};

static int send_uevent(struct power_state_drvdata *drv, enum ps_event_type event)
{
	char event_string[STRING_LEN];
	char *envp[2] = { event_string, NULL };

	scnprintf(event_string, ARRAY_SIZE(event_string), "POWER_STATE_EVENT = %d", event);
	return kobject_uevent_env(&drv->ps_dev->kobj, KOBJ_CHANGE, envp);
}

static int ps_ssr_cb(struct notifier_block *nb, unsigned long opcode, void *data)
{
	struct qcom_ssr_notify_data *notify_data = data;
	struct subsystem_data *ss_data;
	bool ss_present = false;

	list_for_each_entry(ss_data, &drv->sub_sys_list, list) {
		if (!strcmp(ss_data->name, notify_data->name)) {
			ss_present = true;
			break;
		}
	}

	if (!ss_present || ss_data->ignore_ssr)
		return NOTIFY_DONE;

	switch (opcode) {
	case QCOM_SSR_BEFORE_SHUTDOWN:
		pr_debug("%s is shutdown\n", notify_data->name);
		send_uevent(drv, ss_data->enter);
		break;
	case QCOM_SSR_AFTER_POWERUP:
		pr_debug("%s: is powered up\n", notify_data->name);
		send_uevent(drv, ss_data->exit);
		break;
	default:
		pr_debug("%s: ignore ssr event\n", notify_data->name);
		break;
	}

	return NOTIFY_DONE;
}

static int ps_pm_cb(struct notifier_block *nb, unsigned long event, void *unused)
{
	struct power_state_drvdata *drv = container_of(nb, struct power_state_drvdata, ps_pm_nb);
	int ret;

	switch (event) {
	case PM_SUSPEND_PREPARE:
		if (drv->current_state == DEEPSLEEP) {
			pr_info("Deep Sleep entry\n");
			ret = send_deep_sleep_vote(DS_ENTRY, drv);
			if (ret)
				return NOTIFY_BAD;
			pm_set_suspend_via_firmware();
		} else {
			pr_debug("RBSC Suspend\n");
		}
		break;

	case PM_POST_SUSPEND:
		if (pm_suspend_via_firmware()) {
			pr_info("Deep Sleep exit\n");

			ret = send_deep_sleep_vote(DS_EXIT, drv);
			if (ret)
				BUG_ON(1);
			__pm_stay_awake(drv->ps_ws);
			send_uevent(drv, EXIT_DEEP_SLEEP);
		} else {
			pr_debug("RBSC Resume\n");
		}
		break;

	case PM_HIBERNATION_PREPARE:
		pr_info("Hibernate entry\n");

		send_uevent(drv, PREPARE_FOR_HIBERNATION);
		drv->current_state = HIBERNATE;
		break;

	case PM_RESTORE_PREPARE:
		pr_debug("Hibernate prepare\n");
		break;

	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
		pr_info("Hibernate exit\n");
		send_uevent(drv, EXIT_HIBERNATE);
		break;

	default:
		WARN_ONCE(1, "Default case: PM Notifier\n");
		break;
	}

	return NOTIFY_DONE;
}

static void power_state_resume(void)
{
	struct arm_smccc_res res;

	if (pm_suspend_via_firmware()) {
		arm_smccc_smc(DS_ENTRY_SMC_ID, DS_NUM_PARAMETERS, DS_EXIT, 0, 0, 0, 0, 0, &res);
		drv->deep_sleep_cnt += 1;
	}
}

static int power_state_suspend(void)
{
	struct arm_smccc_res res;

	if (pm_suspend_via_firmware()) {
		arm_smccc_smc(DS_ENTRY_SMC_ID, DS_NUM_PARAMETERS, DS_ENTRY, 0, 0, 0, 0, 0, &res);
		if (res.a0)
			return res.a0;
	}

	return 0;
}

static ssize_t deep_sleep_allowed_show(struct kobject *kobj, struct kobj_attribute *attr,
				       char *buf)
{
	struct power_state_drvdata *drv =
				container_of(attr, struct power_state_drvdata, ds_allow_ka);

	return scnprintf(buf, PAGE_SIZE, "%u\n", drv->deep_sleep_allowed);
}

static ssize_t deep_sleep_allowed_store(struct kobject *kobj, struct kobj_attribute *attr,
					const char *buf, size_t count)
{
	struct power_state_drvdata *drv =
				container_of(attr, struct power_state_drvdata, ds_allow_ka);
	int val;
	int ret;

	ret = kstrtoint(buf, 10, &val);
	if (ret) {
		pr_err("Invalid argument passed\n");
		return ret;
	}

	drv->deep_sleep_allowed = val;

	return count;
}

static ssize_t deep_sleep_cnt_show(struct kobject *kobj, struct kobj_attribute *attr,
				       char *buf)
{
	struct power_state_drvdata *drv = container_of(attr, struct power_state_drvdata, ds_ka);

	return scnprintf(buf, PAGE_SIZE, "%u\n", drv->deep_sleep_cnt);
}

static ssize_t deep_sleep_cnt_store(struct kobject *kobj, struct kobj_attribute *attr,
					const char *buf, size_t count)
{
	struct power_state_drvdata *drv = container_of(attr, struct power_state_drvdata, ds_ka);
	int val;
	int ret;

	ret = kstrtoint(buf, 10, &val);
	if (ret) {
		pr_err("Invalid argument passed\n");
		return ret;
	}

	drv->deep_sleep_cnt = val;

	return count;
}

static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
	struct power_state_drvdata *drv = container_of(attr, struct power_state_drvdata, ps_ka);
	int len = strlen(power_state[drv->current_state]);

	return scnprintf(buf, len + 2, "%s\n", power_state[drv->current_state]);
}

static int power_state_dev_init(struct power_state_drvdata *drv)
{
	int ret;

	ret = alloc_chrdev_region(&drv->ps_dev_no, POWER_STATS_BASEMINOR,
				  POWER_STATS_MAX_MINOR, POWER_STATE_DEVICE_NAME);
	if (ret)
		return ret;

	cdev_init(&drv->ps_cdev, &ps_fops);
	ret = cdev_add(&drv->ps_cdev, drv->ps_dev_no, 1);
	if (ret) {
		unregister_chrdev_region(drv->ps_dev_no, 1);
		return ret;
	}

	drv->ps_class = class_create(THIS_MODULE, POWER_STATE_DEVICE_NAME);
	if (IS_ERR_OR_NULL(drv->ps_class)) {
		cdev_del(&drv->ps_cdev);
		unregister_chrdev_region(drv->ps_dev_no, 1);
		return PTR_ERR(drv->ps_class);
	}

	drv->ps_dev = device_create(drv->ps_class, NULL,
				    drv->ps_dev_no, NULL, POWER_STATE_DEVICE_NAME);
	if (IS_ERR_OR_NULL(drv->ps_dev)) {
		class_destroy(drv->ps_class);
		cdev_del(&drv->ps_cdev);
		unregister_chrdev_region(drv->ps_dev_no, 1);
		return PTR_ERR(drv->ps_dev);
	}

	drv->ps_kobj = kobject_create_and_add(POWER_STATE_DEVICE_NAME, kernel_kobj);
	if (!drv->ps_kobj) {
		ret = -ENOMEM;
		device_destroy(drv->ps_class, drv->ps_dev_no);
		class_destroy(drv->ps_class);
		cdev_del(&drv->ps_cdev);
		unregister_chrdev_region(drv->ps_dev_no, 1);
		return ret;
	}

	sysfs_attr_init(&drv->ps_ka.attr);
	drv->ps_ka.attr.mode = 0444;
	drv->ps_ka.attr.name = "state";
	drv->ps_ka.show = state_show;

	ret = sysfs_create_file(drv->ps_kobj, &drv->ps_ka.attr);
	if (ret)
		goto exit;

	sysfs_attr_init(&drv->ds_ka.attr);
	drv->ds_ka.attr.mode = 0644;
	drv->ds_ka.attr.name = "deep_sleep_cnt";
	drv->ds_ka.show = deep_sleep_cnt_show;
	drv->ds_ka.store = deep_sleep_cnt_store;

	ret = sysfs_create_file(drv->ps_kobj, &drv->ds_ka.attr);
	if (ret) {
		sysfs_remove_file(drv->ps_kobj, &drv->ps_ka.attr);
		goto exit;
	}

	sysfs_attr_init(&drv->ds_allow_ka.attr);
	drv->ds_allow_ka.attr.mode = 0644;
	drv->ds_allow_ka.attr.name = "deep_sleep_allowed";
	drv->ds_allow_ka.show = deep_sleep_allowed_show;
	drv->ds_allow_ka.store = deep_sleep_allowed_store;

	ret = sysfs_create_file(drv->ps_kobj, &drv->ds_allow_ka.attr);
	if (ret) {
		sysfs_remove_file(drv->ps_kobj, &drv->ps_ka.attr);
		sysfs_remove_file(drv->ps_kobj, &drv->ds_ka.attr);
		goto exit;
	}

	return 0;

exit:
	kobject_put(drv->ps_kobj);
	device_destroy(drv->ps_class, drv->ps_dev_no);
	class_destroy(drv->ps_class);
	cdev_del(&drv->ps_cdev);
	unregister_chrdev_region(drv->ps_dev_no, 1);

	return ret;
}

static int power_state_probe(struct platform_device *pdev)
{
	struct device_node *dn = pdev->dev.of_node;
	struct subsystem_data *ss_data;
	int ret, i, j;
	const char *name;
	phandle rproc_handle;

	drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
	if (!drv)
		return -ENOMEM;

	if (IS_ENABLED(CONFIG_NOTIFY_AOP)) {
		drv->qmp = qmp_get(&pdev->dev);
		if (IS_ERR(drv->qmp))
			return -EPROBE_DEFER;
	}

	drv->ps_pm_nb.notifier_call = ps_pm_cb;
	drv->ps_pm_nb.priority = PS_PM_NOTIFIER_PRIORITY;
	ret = register_pm_notifier(&drv->ps_pm_nb);
	if (ret)
		return ret;

	drv->ps_ws = wakeup_source_register(&pdev->dev, POWER_STATE_DEVICE_NAME);
	if (!drv->ps_ws)
		goto remove_pm_notifier;

	INIT_LIST_HEAD(&drv->sub_sys_list);

	drv->subsys_count = of_property_count_strings(dn, "qcom,subsys-name");
	if (drv->subsys_count < 0)
		drv->subsys_count = 0;
	for (i = 0; i < drv->subsys_count; i++) {
		of_property_read_string_index(dn, "qcom,subsys-name", i, &name);

		ret = of_property_read_u32_index(dn, "qcom,rproc-handle", i, &rproc_handle);
		if (ret)
			goto remove_ss;

		ss_data = devm_kzalloc(&pdev->dev, sizeof(struct subsystem_data), GFP_KERNEL);
		if (!ss_data) {
			ret = -ENOMEM;
			goto remove_ss;
		}

		ss_data->name = kstrdup_const(name, GFP_KERNEL);
		ss_data->rproc_handle = rproc_handle;

		ss_data->ps_ssr_nb.notifier_call = ps_ssr_cb;
		ss_data->ps_ssr_nb.priority = PS_SSR_NOTIFIER_PRIORITY;
		ss_data->ssr_handle = qcom_register_ssr_notifier(name, &ss_data->ps_ssr_nb);
		ss_data->ignore_ssr = false;
		if (IS_ERR(ss_data->ssr_handle)) {
			ret = PTR_ERR(ss_data->ssr_handle);
			goto remove_ss;
		}

		for (j = 0; j < ARRAY_SIZE(event_data); j++) {
			if (!strcmp(event_data[j].name, name)) {
				ss_data->enter = event_data[j].enter;
				ss_data->exit = event_data[j].exit;
			}
		}
		if (!ss_data->enter) {
			ret = -ENODEV;
			goto remove_ss;
		}

		list_add_tail(&ss_data->list, &drv->sub_sys_list);
	}

	ret = power_state_dev_init(drv);
	if (ret)
		goto remove_ss;

	drv->ps_ops.suspend = power_state_suspend;
	drv->ps_ops.resume = power_state_resume;
	register_syscore_ops(&drv->ps_ops);

	dev_set_drvdata(&pdev->dev, drv);
	return ret;

remove_ss:
	list_for_each_entry(ss_data, &drv->sub_sys_list, list) {
		qcom_unregister_ssr_notifier(ss_data->ssr_handle, &ss_data->ps_ssr_nb);
		list_del(&ss_data->list);
	}
	INIT_LIST_HEAD(&drv->sub_sys_list);
	wakeup_source_unregister(drv->ps_ws);
remove_pm_notifier:
	unregister_pm_notifier(&drv->ps_pm_nb);
	return ret;
}

static int power_state_remove(struct platform_device *pdev)
{
	struct power_state_drvdata *drv = dev_get_drvdata(&pdev->dev);
	struct subsystem_data *ss_data;

	unregister_syscore_ops(&drv->ps_ops);
	if (IS_ENABLED(CONFIG_NOTIFY_AOP))
		qmp_put(drv->qmp);

	list_for_each_entry(ss_data, &drv->sub_sys_list, list) {
		qcom_unregister_ssr_notifier(ss_data->ssr_handle, &ss_data->ps_ssr_nb);
		list_del(&ss_data->list);
	}

	INIT_LIST_HEAD(&drv->sub_sys_list);
	wakeup_source_unregister(drv->ps_ws);
	sysfs_remove_file(drv->ps_kobj, &drv->ds_ka.attr);
	sysfs_remove_file(drv->ps_kobj, &drv->ps_ka.attr);
	kobject_put(drv->ps_kobj);
	device_destroy(drv->ps_class, drv->ps_dev_no);
	class_destroy(drv->ps_class);
	cdev_del(&drv->ps_cdev);
	unregister_chrdev_region(drv->ps_dev_no, 1);
	unregister_pm_notifier(&drv->ps_pm_nb);

	return 0;
}

static const struct of_device_id power_state_of_match[] = {
	{ .compatible = "qcom,power-state", },
	{ }
};
MODULE_DEVICE_TABLE(of, power_state_of_match);

static struct platform_driver power_state_driver = {
	.probe = power_state_probe,
	.remove = power_state_remove,
	.driver = {
		.name = "power-state",
		.of_match_table = power_state_of_match,
		.suppress_bind_attrs = true,
	},
};

module_platform_driver(power_state_driver);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Power State Driver");
MODULE_LICENSE("GPL v2");
