/* Copyright 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* System hooks for Chrome EC */

#ifndef __CROS_EC_HOOKS_H
#define __CROS_EC_HOOKS_H

#include "common.h"

enum hook_priority {
	/* Generic values across all hooks */
	HOOK_PRIO_FIRST = 1,       /* Highest priority */
	HOOK_PRIO_DEFAULT = 5000,  /* Default priority */
	HOOK_PRIO_LAST = 9999,     /* Lowest priority */

	/* Specific hook vales for HOOK_INIT */
	/* DMA inits before ADC, I2C, SPI */
	HOOK_PRIO_INIT_DMA = HOOK_PRIO_FIRST + 1,
	/* LPC inits before modules which need memory-mapped I/O */
	HOOK_PRIO_INIT_LPC = HOOK_PRIO_FIRST + 1,
	/*
	 * I2C dependents (battery, sensors, etc), everything but the
	 * controllers. I2C controller is now initialized in main.c
	 * TODO(b/138384267): Split this hook up and name the resulting
	 * ones more semantically.
	 */
	HOOK_PRIO_INIT_I2C = HOOK_PRIO_FIRST + 2,
	/* Chipset inits before modules which need to know its initial state. */
	HOOK_PRIO_INIT_CHIPSET = HOOK_PRIO_FIRST + 3,
	/* Lid switch inits before power button */
	HOOK_PRIO_INIT_LID = HOOK_PRIO_FIRST + 4,
	/* Power button inits before chipset and switch */
	HOOK_PRIO_INIT_POWER_BUTTON = HOOK_PRIO_FIRST + 5,
	/* Init switch states after power button / lid */
	HOOK_PRIO_INIT_SWITCH = HOOK_PRIO_FIRST + 6,
	/* Init fan before PWM */
	HOOK_PRIO_INIT_FAN = HOOK_PRIO_FIRST + 7,
	/* PWM inits before modules which might use it (LEDs) */
	HOOK_PRIO_INIT_PWM = HOOK_PRIO_FIRST + 8,
	/* SPI inits before modules which might use it (sensors) */
	HOOK_PRIO_INIT_SPI = HOOK_PRIO_FIRST + 9,
	/* Extpower inits before modules which might use it (battery, LEDs) */
	HOOK_PRIO_INIT_EXTPOWER = HOOK_PRIO_FIRST + 10,
	/* Init VBOOT hash later, since it depends on deferred functions */
	HOOK_PRIO_INIT_VBOOT_HASH = HOOK_PRIO_FIRST + 11,
	/* Init charge manager before usage in board init */
	HOOK_PRIO_CHARGE_MANAGER_INIT = HOOK_PRIO_FIRST + 12,

	HOOK_PRIO_INIT_ADC = HOOK_PRIO_DEFAULT,
	HOOK_PRIO_INIT_DAC = HOOK_PRIO_DEFAULT,

	/* Specific values to lump temperature-related hooks together */
	HOOK_PRIO_TEMP_SENSOR = 6000,
	/* After all sensors have been polled */
	HOOK_PRIO_TEMP_SENSOR_DONE = HOOK_PRIO_TEMP_SENSOR + 1,
};

enum hook_type {
	/*
	 * System initialization.
	 *
	 * Hook routines are called from main(), after all hard-coded inits,
	 * before task scheduling is enabled.
	 */
	HOOK_INIT = 0,

	/*
	 * System clock changed frequency.
	 *
	 * The "pre" frequency hook is called before we change the frequency.
	 * There is no way to cancel.  Hook routines are always called from
	 * a task, so it's OK to lock a mutex here.  However, they may be called
	 * from a deferred task on some platforms so callbacks must make sure
	 * not to do anything that would require some other deferred task to
	 * run.
	 */
	HOOK_PRE_FREQ_CHANGE,
	HOOK_FREQ_CHANGE,

	/*
	 * About to jump to another image.  Modules which need to preserve data
	 * across such a jump should save it here and restore it in HOOK_INIT.
	 *
	 * Hook routines are called from the context which initiates the jump,
	 * WITH INTERRUPTS DISABLED.
	 */
	HOOK_SYSJUMP,

	/*
	 * Initialization for components such as PMU to be done before host
	 * chipset/AP starts up.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_PRE_INIT,

	/* System is starting up.  All suspend rails are now on.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_STARTUP,

	/*
	 * System is resuming from suspend, or booting and has reached the
	 * point where all voltage rails are on.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_RESUME,

	/*
	 * System is suspending, or shutting down; all voltage rails are still
	 * on.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_SUSPEND,

#ifdef CONFIG_CHIPSET_RESUME_INIT_HOOK
	/*
	 * Initialization before the system resumes, like enabling the SPI
	 * driver such that it can receive a host resume event.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_RESUME_INIT,

	/*
	 * System has suspended. It is paired with CHIPSET_RESUME_INIT hook,
	 * like reverting the initialization of the SPI driver.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_SUSPEND_COMPLETE,
#endif

	/*
	 * System is shutting down.  All suspend rails are still on.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_SHUTDOWN,

	/*
	 * System has already shut down. All the suspend rails are already off.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_SHUTDOWN_COMPLETE,

	/*
	 * System is in G3.  All power rails are now turned off.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_HARD_OFF,

	/*
	 * System reset in S0.  All rails are still up.
	 *
	 * Hook routines are called from the chipset task.
	 */
	HOOK_CHIPSET_RESET,

	/*
	 * AC power plugged in or removed.
	 *
	 * Hook routines are called from the TICK task.
	 */
	HOOK_AC_CHANGE,

	/*
	 * Lid opened or closed.  Based on debounced lid state, not raw lid
	 * GPIO input.
	 *
	 * Hook routines are called from the TICK task.
	 */
	HOOK_LID_CHANGE,

	/*
	 * Device in tablet mode (base behind lid).
	 *
	 * Hook routines are called from the TICK task.
	 */
	HOOK_TABLET_MODE_CHANGE,

	/*
	 * Detachable device connected to a base.
	 *
	 * Hook routines are called from the TICK task.
	 */
	HOOK_BASE_ATTACHED_CHANGE,

	/*
	 * Power button pressed or released.  Based on debounced power button
	 * state, not raw GPIO input.
	 *
	 * Hook routines are called from the TICK task.
	 */
	HOOK_POWER_BUTTON_CHANGE,

	/*
	 * Battery state of charge changed
	 *
	 * Hook routines are called from the charger task.
	 */
	HOOK_BATTERY_SOC_CHANGE,

#ifdef CONFIG_USB_SUSPEND
	/*
	 * Called when there is a change in USB power management status
	 * (suspended or resumed).
	 *
	 * Hook routines are called from HOOKS task.
	 */
	HOOK_USB_PM_CHANGE,
#endif

	/*
	 * Periodic tick, every HOOK_TICK_INTERVAL.
	 *
	 * Hook routines will be called from the TICK task.
	 */
	HOOK_TICK,

	/*
	 * Periodic tick, every second.
	 *
	 * Hook routines will be called from the TICK task.
	 */
	HOOK_SECOND,

	/*
	 * USB PD cc disconnect event.
	 */
	HOOK_USB_PD_DISCONNECT,

	/*
	 * USB PD cc connection event.
	 */
	HOOK_USB_PD_CONNECT,

#ifdef TEST_BUILD
	/*
	 * Special hook types to be used by unit tests of the hooks
	 * implementation only.
	 */
	HOOK_TEST_1,
	HOOK_TEST_2,
	HOOK_TEST_3,
#endif  /* TEST_BUILD */

	/*
	 * Not a hook type (instead the number of hooks). This should
	 * always be placed at the end of this enumeration.
	 */
	HOOK_TYPE_COUNT,
};

struct hook_data {
	/* Hook processing routine. */
	void (*routine)(void);
	/* Priority; low numbers = higher priority. */
	int priority;
};

/**
 * Call all the hook routines of a specified type.
 *
 * This function must be called from the correct type-specific context (task);
 * see enum hook_type for details.  hook_notify() should NEVER be called from
 * interrupt context unless specifically allowed for a hook type, because hook
 * routines may need to perform task-level calls like usleep() and mutex
 * operations that are not valid in interrupt context.  Instead of calling a
 * hook from interrupt context, use a deferred function.
 *
 * @param type		Type of hook routines to call.
 */
void hook_notify(enum hook_type type);

/*
 * CONFIG_PLATFORM_EC_HOOKS is enabled by default during a Zephyr
 * build, but can be disabled via Kconfig if desired (leaving the stub
 * implementation at the bottom of this file).
 */
#if defined(CONFIG_PLATFORM_EC_HOOKS)
#include "zephyr_hooks_shim.h"
#elif defined(CONFIG_COMMON_RUNTIME)
struct deferred_data {
	/* Deferred function pointer */
	void (*routine)(void);
};

/**
 * Start a timer to call a deferred routine.
 *
 * The routine will be called after at least the specified delay, in the
 * context of the hook task.
 *
 * @param data	The deferred_data struct created by invoking DECLARE_DEFERRED().
 * @param us	Delay in microseconds until routine will be called.  If the
 *		routine is already pending, subsequent calls will change the
 *		delay.  Pass us=0 to call as soon as possible, or -1 to cancel
 *		the deferred call.
 *
 * @return non-zero if error.
 */
int hook_call_deferred(const struct deferred_data *data, int us);

/**
 * Register a hook routine.
 *
 * NOTE: Hook routines must be careful not to leave resources locked which may
 * be needed by other hook routines or deferred function calls.  This can cause
 * a deadlock, because most hooks and all deferred functions are called from
 * the same hook task.  For example:
 *
 *   hook1(): lock foo
 *   deferred1(): lock foo, use foo, unlock foo
 *   hook2(): unlock foo
 *
 * In this example, hook1() and hook2() lock and unlock a shared resource foo
 * (for example, a mutex).  If deferred1() attempts to lock the resource, it
 * will stall waiting for the resource to be unlocked.  But the unlock will
 * never happen, because hook2() won't be called by the hook task until
 * deferred1() returns.
 *
 * @param hooktype	Type of hook for routine (enum hook_type)
 * @param routine	Hook routine, with prototype void routine(void)
 * @param priority      Priority for determining when routine is called vs.
 *			other hook routines; should be between HOOK_PRIO_FIRST
 *                      and HOOK_PRIO_LAST, and should be HOOK_PRIO_DEFAULT
 *			unless there's a compelling reason to care about the
 *			order in which hooks are called.
 */
#define DECLARE_HOOK(hooktype, routine, priority)			\
	const struct hook_data __keep __no_sanitize_address		\
	CONCAT4(__hook_, hooktype, _, routine)				\
	__attribute__((section(".rodata." STRINGIFY(hooktype))))	\
	     = {routine, priority}

/**
 * Register a deferred function call.
 *
 * DECLARE_DEFERRED creates a new deferred_data struct with a name constructed
 * by concatenating _data to the name of the routine passed.
 *
 * To call a deferred routine defined as:
 *     DECLARE_DEFERRED(foo)
 * You would call
 *     hook_call_deferred(&foo_data, delay_in_microseconds);
 *
 * NOTE: Deferred function call routines must be careful not to leave resources
 * locked which may be needed by other hook routines or deferred function
 * calls.  This can cause a deadlock, because most hooks and all deferred
 * functions are called from the same hook task.  See DECLARE_HOOK() for an
 * example.
 *
 * @param routine	Function pointer, with prototype void routine(void)
 */
#define DECLARE_DEFERRED(routine)					\
	const struct deferred_data __keep __no_sanitize_address		\
	CONCAT2(routine, _data)						\
	__attribute__((section(".rodata.deferred")))			\
	     = {routine}
#else
/*
 * Stub implementation in case hooks are disabled (neither
 * CONFIG_COMMON_RUNTIME nor CONFIG_PLATFORM_EC_HOOKS is defined)
 */
#define hook_call_deferred(unused1, unused2) -1
#define DECLARE_HOOK(t, func, p)				\
	void CONCAT2(unused_hook_, func)(void) { func(); }
#define DECLARE_DEFERRED(func)					\
	void CONCAT2(unused_deferred_, func)(void) { func(); }
#endif

#endif  /* __CROS_EC_HOOKS_H */
