// SPDX-License-Identifier: GPL-2.0 
 | 
/* 
 | 
 * kernel/power/autosleep.c 
 | 
 * 
 | 
 * Opportunistic sleep support. 
 | 
 * 
 | 
 * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl> 
 | 
 */ 
 | 
  
 | 
#include <linux/device.h> 
 | 
#include <linux/mutex.h> 
 | 
#include <linux/pm_wakeup.h> 
 | 
  
 | 
#include "power.h" 
 | 
  
 | 
static suspend_state_t autosleep_state; 
 | 
static struct workqueue_struct *autosleep_wq; 
 | 
/* 
 | 
 * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source 
 | 
 * is active, otherwise a deadlock with try_to_suspend() is possible. 
 | 
 * Alternatively mutex_lock_interruptible() can be used.  This will then fail 
 | 
 * if an auto_sleep cycle tries to freeze processes. 
 | 
 */ 
 | 
static DEFINE_MUTEX(autosleep_lock); 
 | 
static struct wakeup_source *autosleep_ws; 
 | 
  
 | 
static void try_to_suspend(struct work_struct *work) 
 | 
{ 
 | 
    unsigned int initial_count, final_count; 
 | 
  
 | 
    if (!pm_get_wakeup_count(&initial_count, true)) 
 | 
        goto out; 
 | 
  
 | 
    mutex_lock(&autosleep_lock); 
 | 
  
 | 
    if (!pm_save_wakeup_count(initial_count) || 
 | 
        system_state != SYSTEM_RUNNING) { 
 | 
        mutex_unlock(&autosleep_lock); 
 | 
        goto out; 
 | 
    } 
 | 
  
 | 
    if (autosleep_state == PM_SUSPEND_ON) { 
 | 
        mutex_unlock(&autosleep_lock); 
 | 
        return; 
 | 
    } 
 | 
    if (autosleep_state >= PM_SUSPEND_MAX) 
 | 
        hibernate(); 
 | 
    else 
 | 
        pm_suspend(autosleep_state); 
 | 
  
 | 
    mutex_unlock(&autosleep_lock); 
 | 
  
 | 
    if (!pm_get_wakeup_count(&final_count, false)) 
 | 
        goto out; 
 | 
  
 | 
    /* 
 | 
     * If the wakeup occured for an unknown reason, wait to prevent the 
 | 
     * system from trying to suspend and waking up in a tight loop. 
 | 
     */ 
 | 
    if (final_count == initial_count) 
 | 
        schedule_timeout_uninterruptible(HZ / 2); 
 | 
  
 | 
 out: 
 | 
    queue_up_suspend_work(); 
 | 
} 
 | 
  
 | 
static DECLARE_WORK(suspend_work, try_to_suspend); 
 | 
  
 | 
void queue_up_suspend_work(void) 
 | 
{ 
 | 
    if (autosleep_state > PM_SUSPEND_ON) 
 | 
        queue_work(autosleep_wq, &suspend_work); 
 | 
} 
 | 
  
 | 
suspend_state_t pm_autosleep_state(void) 
 | 
{ 
 | 
    return autosleep_state; 
 | 
} 
 | 
  
 | 
int pm_autosleep_lock(void) 
 | 
{ 
 | 
    return mutex_lock_interruptible(&autosleep_lock); 
 | 
} 
 | 
  
 | 
void pm_autosleep_unlock(void) 
 | 
{ 
 | 
    mutex_unlock(&autosleep_lock); 
 | 
} 
 | 
  
 | 
int pm_autosleep_set_state(suspend_state_t state) 
 | 
{ 
 | 
  
 | 
#ifndef CONFIG_HIBERNATION 
 | 
    if (state >= PM_SUSPEND_MAX) 
 | 
        return -EINVAL; 
 | 
#endif 
 | 
  
 | 
    __pm_stay_awake(autosleep_ws); 
 | 
  
 | 
    mutex_lock(&autosleep_lock); 
 | 
  
 | 
    autosleep_state = state; 
 | 
  
 | 
    __pm_relax(autosleep_ws); 
 | 
  
 | 
    if (state > PM_SUSPEND_ON) { 
 | 
        pm_wakep_autosleep_enabled(true); 
 | 
        queue_up_suspend_work(); 
 | 
    } else { 
 | 
        pm_wakep_autosleep_enabled(false); 
 | 
    } 
 | 
  
 | 
    mutex_unlock(&autosleep_lock); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int __init pm_autosleep_init(void) 
 | 
{ 
 | 
    autosleep_ws = wakeup_source_register(NULL, "autosleep"); 
 | 
    if (!autosleep_ws) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    autosleep_wq = alloc_ordered_workqueue("autosleep", 0); 
 | 
    if (autosleep_wq) 
 | 
        return 0; 
 | 
  
 | 
    wakeup_source_unregister(autosleep_ws); 
 | 
    return -ENOMEM; 
 | 
} 
 |