blob: dc7b5f9cdb630cb6627aed75c43f8d060f80a656 [file] [log] [blame]
/**
Copyright (c) 2008 - 2013 Quantenna Communications Inc
All Rights Reserved
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/module.h>
#include <asm/board/pm.h>
#include "qdrv_sch_pm.h"
#include <qtn/qdrv_sch.h>
#if QDRV_SCH_PM
atomic_t qdrv_sch_pm_enqueued_counter = ATOMIC_INIT(0);
static unsigned int pm_slow_counter = 0;
static unsigned int pm_avg_enqueued = 0;
static atomic_t pm_slow_state = ATOMIC_INIT(0);
static atomic_t pm_init_counter = ATOMIC_INIT(0);
static struct timer_list pm_timer;
static struct delayed_work pm_work;
static inline void qdrv_sch_pm_queue_work(unsigned long delay)
{
pm_slow_counter = 0;
pm_avg_enqueued = 0;
pm_queue_work(&pm_work, delay);
}
static void qdrv_sch_pm_wq_func(struct work_struct *work)
{
pm_qos_update_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_QDISC,
atomic_read(&pm_slow_state) ? BOARD_PM_LEVEL_SLOW_DOWN : PM_QOS_DEFAULT_VALUE);
}
static void qdrv_sch_pm_timer_func(unsigned long data)
{
int enqueued = atomic_xchg(&qdrv_sch_pm_enqueued_counter, 0);
/* calculate average enqueued packets number */
if (pm_avg_enqueued == 0) {
pm_avg_enqueued = (enqueued << 1);
} else {
pm_avg_enqueued = ((enqueued + pm_avg_enqueued) >> 1);
}
/* update counter of how long low level of traffic observed */
if ((enqueued >= BOARD_PM_QDISC_SPEEDUP_THRESHOLD) ||
(pm_avg_enqueued >= BOARD_PM_QDISC_SLOWDOWN_THRESHOLD)) {
pm_slow_counter = 0;
} else if (pm_slow_counter < BOARD_PM_QDISC_SLOWDOWN_COUNT) {
++pm_slow_counter;
}
/* handle state transition */
if (enqueued >= BOARD_PM_QDISC_SPEEDUP_THRESHOLD) {
if (atomic_xchg(&pm_slow_state, 0)) {
qdrv_sch_pm_queue_work(BOARD_PM_QDISC_DEFAULT_TIMEOUT);
}
} else if (pm_slow_counter >= BOARD_PM_QDISC_SLOWDOWN_COUNT) {
if (!atomic_xchg(&pm_slow_state, 1)) {
qdrv_sch_pm_queue_work(BOARD_PM_QDISC_SLOWDOWN_TIMEOUT);
}
}
/* restart timer */
mod_timer(&pm_timer, jiffies + BOARD_PM_QDISC_TIMER_TIMEOUT);
}
void qdrv_sch_pm_init(void)
{
if (atomic_add_return(1, &pm_init_counter) != 1) {
return;
}
pm_qos_add_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_QDISC, BOARD_PM_LEVEL_SLOW_DOWN);
INIT_DELAYED_WORK(&pm_work, qdrv_sch_pm_wq_func);
init_timer(&pm_timer);
pm_timer.function = qdrv_sch_pm_timer_func;
pm_timer.expires = jiffies + BOARD_PM_QDISC_TIMER_TIMEOUT;
add_timer(&pm_timer);
atomic_set(&pm_slow_state, 1);
}
void qdrv_sch_pm_exit(void)
{
if (atomic_sub_return(1, &pm_init_counter) != 0) {
return;
}
del_timer(&pm_timer);
pm_flush_work(&pm_work);
pm_qos_remove_requirement(PM_QOS_POWER_SAVE, BOARD_PM_GOVERNOR_QDISC);
}
#endif /* QDRV_SCH_PM */