/*
 * Copyright (c) 2017 The Linux Foundation. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * DOC: qdf_cpuhp
 * This file provides OS dependent QDF CPU hotplug APIs
 */

#include "i_qdf_cpuhp.h"
#include "qdf_trace.h"
#include "linux/cpu.h"
#include "linux/notifier.h"
#include "linux/version.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
#include "linux/cpuhotplug.h"
#endif

static __qdf_cpuhp_emit __qdf_cpuhp_on_up;
static __qdf_cpuhp_emit __qdf_cpuhp_on_down;

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0)
static int qdf_cpuhp_legacy_handler(struct notifier_block *block,
				    unsigned long state,
				    void *hcpu)
{
	unsigned long cpu = (unsigned long)hcpu;

	switch (state) {
	case CPU_ONLINE:
		__qdf_cpuhp_on_up(cpu);
		break;

	case CPU_DOWN_PREPARE:
	case CPU_DOWN_PREPARE_FROZEN:
		__qdf_cpuhp_on_down(cpu);
		break;

	default:
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block qdf_cpuhp_notifier_block = {
	.notifier_call = qdf_cpuhp_legacy_handler,
};

static inline void qdf_cpuhp_register_callbacks(void)
{
	register_hotcpu_notifier(&qdf_cpuhp_notifier_block);
}

static inline void qdf_cpuhp_unregister_callbacks(void)
{
	unregister_hotcpu_notifier(&qdf_cpuhp_notifier_block);
}
#else
static enum cpuhp_state registered_hotplug_state;

static int qdf_cpuhp_up_handler(unsigned int cpu)
{
	__qdf_cpuhp_on_up(cpu);

	return 0;
}

static int qdf_cpuhp_down_handler(unsigned int cpu)
{
	__qdf_cpuhp_on_down(cpu);

	return 0;
}

static inline void qdf_cpuhp_register_callbacks(void)
{
	registered_hotplug_state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
						     "wlan/qca-qdf:online",
						     qdf_cpuhp_up_handler,
						     qdf_cpuhp_down_handler);
}

static inline void qdf_cpuhp_unregister_callbacks(void)
{
	QDF_BUG(registered_hotplug_state);
	if (registered_hotplug_state)
		cpuhp_remove_state(registered_hotplug_state);
}
#endif /* KERNEL_VERSION(4, 6, 0) */

void __qdf_cpuhp_os_init(__qdf_cpuhp_emit on_up, __qdf_cpuhp_emit on_down)
{
	__qdf_cpuhp_on_up = on_up;
	__qdf_cpuhp_on_down = on_down;

	qdf_cpuhp_register_callbacks();
}

void __qdf_cpuhp_os_deinit(void)
{
	qdf_cpuhp_unregister_callbacks();
}