// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (C) 2011 Google, Inc.3*4* Author:5* Colin Cross <[email protected]>6*/78#include <linux/kernel.h>9#include <linux/cpu_pm.h>10#include <linux/module.h>11#include <linux/notifier.h>12#include <linux/spinlock.h>13#include <linux/syscore_ops.h>1415/*16* atomic_notifiers use a spinlock_t, which can block under PREEMPT_RT.17* Notifications for cpu_pm will be issued by the idle task itself, which can18* never block, IOW it requires using a raw_spinlock_t.19*/20static struct {21struct raw_notifier_head chain;22raw_spinlock_t lock;23} cpu_pm_notifier = {24.chain = RAW_NOTIFIER_INIT(cpu_pm_notifier.chain),25.lock = __RAW_SPIN_LOCK_UNLOCKED(cpu_pm_notifier.lock),26};2728static int cpu_pm_notify(enum cpu_pm_event event)29{30int ret;3132rcu_read_lock();33ret = raw_notifier_call_chain(&cpu_pm_notifier.chain, event, NULL);34rcu_read_unlock();3536return notifier_to_errno(ret);37}3839static int cpu_pm_notify_robust(enum cpu_pm_event event_up, enum cpu_pm_event event_down)40{41unsigned long flags;42int ret;4344raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);45ret = raw_notifier_call_chain_robust(&cpu_pm_notifier.chain, event_up, event_down, NULL);46raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);4748return notifier_to_errno(ret);49}5051/**52* cpu_pm_register_notifier - register a driver with cpu_pm53* @nb: notifier block to register54*55* Add a driver to a list of drivers that are notified about56* CPU and CPU cluster low power entry and exit.57*58* This function has the same return conditions as raw_notifier_chain_register.59*/60int cpu_pm_register_notifier(struct notifier_block *nb)61{62unsigned long flags;63int ret;6465raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);66ret = raw_notifier_chain_register(&cpu_pm_notifier.chain, nb);67raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);68return ret;69}70EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);7172/**73* cpu_pm_unregister_notifier - unregister a driver with cpu_pm74* @nb: notifier block to be unregistered75*76* Remove a driver from the CPU PM notifier list.77*78* This function has the same return conditions as raw_notifier_chain_unregister.79*/80int cpu_pm_unregister_notifier(struct notifier_block *nb)81{82unsigned long flags;83int ret;8485raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);86ret = raw_notifier_chain_unregister(&cpu_pm_notifier.chain, nb);87raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);88return ret;89}90EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);9192/**93* cpu_pm_enter - CPU low power entry notifier94*95* Notifies listeners that a single CPU is entering a low power state that may96* cause some blocks in the same power domain as the cpu to reset.97*98* Must be called on the affected CPU with interrupts disabled. Platform is99* responsible for ensuring that cpu_pm_enter is not called twice on the same100* CPU before cpu_pm_exit is called. Notified drivers can include VFP101* co-processor, interrupt controller and its PM extensions, local CPU102* timers context save/restore which shouldn't be interrupted. Hence it103* must be called with interrupts disabled.104*105* Return conditions are same as __raw_notifier_call_chain.106*/107int cpu_pm_enter(void)108{109return cpu_pm_notify_robust(CPU_PM_ENTER, CPU_PM_ENTER_FAILED);110}111EXPORT_SYMBOL_GPL(cpu_pm_enter);112113/**114* cpu_pm_exit - CPU low power exit notifier115*116* Notifies listeners that a single CPU is exiting a low power state that may117* have caused some blocks in the same power domain as the cpu to reset.118*119* Notified drivers can include VFP co-processor, interrupt controller120* and its PM extensions, local CPU timers context save/restore which121* shouldn't be interrupted. Hence it must be called with interrupts disabled.122*123* Return conditions are same as __raw_notifier_call_chain.124*/125int cpu_pm_exit(void)126{127return cpu_pm_notify(CPU_PM_EXIT);128}129EXPORT_SYMBOL_GPL(cpu_pm_exit);130131/**132* cpu_cluster_pm_enter - CPU cluster low power entry notifier133*134* Notifies listeners that all cpus in a power domain are entering a low power135* state that may cause some blocks in the same power domain to reset.136*137* Must be called after cpu_pm_enter has been called on all cpus in the power138* domain, and before cpu_pm_exit has been called on any cpu in the power139* domain. Notified drivers can include VFP co-processor, interrupt controller140* and its PM extensions, local CPU timers context save/restore which141* shouldn't be interrupted. Hence it must be called with interrupts disabled.142*143* Must be called with interrupts disabled.144*145* Return conditions are same as __raw_notifier_call_chain.146*/147int cpu_cluster_pm_enter(void)148{149return cpu_pm_notify_robust(CPU_CLUSTER_PM_ENTER, CPU_CLUSTER_PM_ENTER_FAILED);150}151EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter);152153/**154* cpu_cluster_pm_exit - CPU cluster low power exit notifier155*156* Notifies listeners that all cpus in a power domain are exiting form a157* low power state that may have caused some blocks in the same power domain158* to reset.159*160* Must be called after cpu_cluster_pm_enter has been called for the power161* domain, and before cpu_pm_exit has been called on any cpu in the power162* domain. Notified drivers can include VFP co-processor, interrupt controller163* and its PM extensions, local CPU timers context save/restore which164* shouldn't be interrupted. Hence it must be called with interrupts disabled.165*166* Return conditions are same as __raw_notifier_call_chain.167*/168int cpu_cluster_pm_exit(void)169{170return cpu_pm_notify(CPU_CLUSTER_PM_EXIT);171}172EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit);173174#ifdef CONFIG_PM175static int cpu_pm_suspend(void)176{177int ret;178179ret = cpu_pm_enter();180if (ret)181return ret;182183ret = cpu_cluster_pm_enter();184return ret;185}186187static void cpu_pm_resume(void)188{189cpu_cluster_pm_exit();190cpu_pm_exit();191}192193static struct syscore_ops cpu_pm_syscore_ops = {194.suspend = cpu_pm_suspend,195.resume = cpu_pm_resume,196};197198static int cpu_pm_init(void)199{200register_syscore_ops(&cpu_pm_syscore_ops);201return 0;202}203core_initcall(cpu_pm_init);204#endif205206207