You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
kernel_samsung_sm7125/drivers/devfreq/devfreq_boost.c

341 lines
8.4 KiB

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018-2021 Sultan Alsawaf <sultan@kerneltoast.com>.
*/
#define pr_fmt(fmt) "devfreq_boost: " fmt
#include <linux/devfreq_boost.h>
#include <linux/input.h>
#include <linux/kthread.h>
#include <linux/msm_drm_notify.h>
#include <linux/slab.h>
#include <uapi/linux/sched/types.h>
enum {
SCREEN_OFF,
INPUT_BOOST,
MAX_BOOST
};
struct boost_dev {
struct devfreq *df;
struct delayed_work input_unboost;
struct delayed_work max_unboost;
wait_queue_head_t boost_waitq;
atomic_long_t max_boost_expires;
unsigned long boost_freq;
unsigned long state;
};
struct df_boost_drv {
struct boost_dev devices[DEVFREQ_MAX];
struct notifier_block msm_drm_notif;
};
static void devfreq_input_unboost(struct work_struct *work);
static void devfreq_max_unboost(struct work_struct *work);
#define BOOST_DEV_INIT(b, dev, freq) .devices[dev] = { \
.input_unboost = \
__DELAYED_WORK_INITIALIZER((b).devices[dev].input_unboost, \
devfreq_input_unboost, 0), \
.max_unboost = \
__DELAYED_WORK_INITIALIZER((b).devices[dev].max_unboost, \
devfreq_max_unboost, 0), \
.boost_waitq = \
__WAIT_QUEUE_HEAD_INITIALIZER((b).devices[dev].boost_waitq), \
.boost_freq = freq \
}
static struct df_boost_drv df_boost_drv_g __read_mostly = {
BOOST_DEV_INIT(df_boost_drv_g, DEVFREQ_CPU_LLCC_DDR_BW,
CONFIG_DEVFREQ_CPU_LLCC_DDR_BW_BOOST_FREQ)
};
static void __devfreq_boost_kick(struct boost_dev *b)
{
if (!READ_ONCE(b->df) || test_bit(SCREEN_OFF, &b->state))
return;
set_bit(INPUT_BOOST, &b->state);
if (!mod_delayed_work(system_unbound_wq, &b->input_unboost,
msecs_to_jiffies(CONFIG_DEVFREQ_INPUT_BOOST_DURATION_MS))) {
/* Set the bit again in case we raced with the unboost worker */
set_bit(INPUT_BOOST, &b->state);
wake_up(&b->boost_waitq);
}
}
void devfreq_boost_kick(enum df_device device)
{
struct df_boost_drv *d = &df_boost_drv_g;
__devfreq_boost_kick(&d->devices[device]);
}
static void __devfreq_boost_kick_max(struct boost_dev *b,
unsigned int duration_ms)
{
unsigned long boost_jiffies, curr_expires, new_expires;
if (!READ_ONCE(b->df) || test_bit(SCREEN_OFF, &b->state))
return;
boost_jiffies = msecs_to_jiffies(duration_ms);
do {
curr_expires = atomic_long_read(&b->max_boost_expires);
new_expires = jiffies + boost_jiffies;
/* Skip this boost if there's a longer boost in effect */
if (time_after(curr_expires, new_expires))
return;
} while (atomic_long_cmpxchg(&b->max_boost_expires, curr_expires,
new_expires) != curr_expires);
set_bit(MAX_BOOST, &b->state);
if (!mod_delayed_work(system_unbound_wq, &b->max_unboost,
boost_jiffies)) {
/* Set the bit again in case we raced with the unboost worker */
set_bit(MAX_BOOST, &b->state);
wake_up(&b->boost_waitq);
}
}
void devfreq_boost_kick_max(enum df_device device, unsigned int duration_ms)
{
struct df_boost_drv *d = &df_boost_drv_g;
__devfreq_boost_kick_max(&d->devices[device], duration_ms);
}
void devfreq_register_boost_device(enum df_device device, struct devfreq *df)
{
struct df_boost_drv *d = &df_boost_drv_g;
struct boost_dev *b;
df->is_boost_device = true;
b = &d->devices[device];
WRITE_ONCE(b->df, df);
}
static void devfreq_input_unboost(struct work_struct *work)
{
struct boost_dev *b = container_of(to_delayed_work(work), typeof(*b),
input_unboost);
clear_bit(INPUT_BOOST, &b->state);
wake_up(&b->boost_waitq);
}
static void devfreq_max_unboost(struct work_struct *work)
{
struct boost_dev *b = container_of(to_delayed_work(work), typeof(*b),
max_unboost);
clear_bit(MAX_BOOST, &b->state);
wake_up(&b->boost_waitq);
}
static void devfreq_update_boosts(struct boost_dev *b, unsigned long state)
{
struct devfreq *df = b->df;
mutex_lock(&df->lock);
if (state & BIT(SCREEN_OFF)) {
df->min_freq = df->profile->freq_table[0];
df->max_boost = false;
} else {
df->min_freq = state & BIT(INPUT_BOOST) ?
min(b->boost_freq, df->max_freq) :
df->profile->freq_table[0];
df->max_boost = state & BIT(MAX_BOOST);
}
update_devfreq(df);
mutex_unlock(&df->lock);
}
static int devfreq_boost_thread(void *data)
{
static const struct sched_param sched_max_rt_prio = {
.sched_priority = MAX_RT_PRIO - 1
};
struct boost_dev *b = data;
unsigned long old_state = 0;
sched_setscheduler_nocheck(current, SCHED_FIFO, &sched_max_rt_prio);
while (1) {
bool should_stop = false;
unsigned long curr_state;
wait_event_interruptible(b->boost_waitq,
(curr_state = READ_ONCE(b->state)) != old_state ||
(should_stop = kthread_should_stop()));
if (should_stop)
break;
if (old_state != curr_state) {
devfreq_update_boosts(b, curr_state);
old_state = curr_state;
}
}
return 0;
}
static int msm_drm_notifier_cb(struct notifier_block *nb,
unsigned long action, void *data)
{
struct df_boost_drv *d = container_of(nb, typeof(*d), msm_drm_notif);
int i, *blank = ((struct msm_drm_notifier *)data)->data;
/* Parse DRM blank events as soon as they occur */
if (action != MSM_DRM_EARLY_EVENT_BLANK)
return NOTIFY_OK;
/* Boost when the screen turns on and unboost when it turns off */
for (i = 0; i < DEVFREQ_MAX; i++) {
struct boost_dev *b = &d->devices[i];
if (*blank == MSM_DRM_BLANK_UNBLANK) {
clear_bit(SCREEN_OFF, &b->state);
__devfreq_boost_kick_max(b,
CONFIG_DEVFREQ_WAKE_BOOST_DURATION_MS);
} else {
set_bit(SCREEN_OFF, &b->state);
wake_up(&b->boost_waitq);
}
}
return NOTIFY_OK;
}
static void devfreq_boost_input_event(struct input_handle *handle,
unsigned int type, unsigned int code,
int value)
{
struct df_boost_drv *d = handle->handler->private;
int i;
for (i = 0; i < DEVFREQ_MAX; i++)
__devfreq_boost_kick(&d->devices[i]);
}
static int devfreq_boost_input_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
struct input_handle *handle;
int ret;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = "devfreq_boost_handle";
ret = input_register_handle(handle);
if (ret)
goto free_handle;
ret = input_open_device(handle);
if (ret)
goto unregister_handle;
return 0;
unregister_handle:
input_unregister_handle(handle);
free_handle:
kfree(handle);
return ret;
}
static void devfreq_boost_input_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
static const struct input_device_id devfreq_boost_ids[] = {
/* Multi-touch touchscreen */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_ABS) },
.absbit = { [BIT_WORD(ABS_MT_POSITION_X)] =
BIT_MASK(ABS_MT_POSITION_X) |
BIT_MASK(ABS_MT_POSITION_Y) }
},
/* Touchpad */
{
.flags = INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
.absbit = { [BIT_WORD(ABS_X)] =
BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) }
},
/* Keypad */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_KEY) }
},
{ }
};
static struct input_handler devfreq_boost_input_handler = {
.event = devfreq_boost_input_event,
.connect = devfreq_boost_input_connect,
.disconnect = devfreq_boost_input_disconnect,
.name = "devfreq_boost_handler",
.id_table = devfreq_boost_ids
};
static int __init devfreq_boost_init(void)
{
struct df_boost_drv *d = &df_boost_drv_g;
struct task_struct *thread[DEVFREQ_MAX];
int i, ret;
for (i = 0; i < DEVFREQ_MAX; i++) {
struct boost_dev *b = &d->devices[i];
thread[i] = kthread_run(devfreq_boost_thread, b,
"devfreq_boostd/%d", i);
if (IS_ERR(thread[i])) {
ret = PTR_ERR(thread[i]);
pr_err("Failed to create kthread, err: %d\n", ret);
goto stop_kthreads;
}
}
devfreq_boost_input_handler.private = d;
ret = input_register_handler(&devfreq_boost_input_handler);
if (ret) {
pr_err("Failed to register input handler, err: %d\n", ret);
goto stop_kthreads;
}
d->msm_drm_notif.notifier_call = msm_drm_notifier_cb;
d->msm_drm_notif.priority = INT_MAX;
ret = msm_drm_register_client(&d->msm_drm_notif);
if (ret) {
pr_err("Failed to register msm_drm notifier, err: %d\n", ret);
goto unregister_handler;
}
return 0;
unregister_handler:
input_unregister_handler(&devfreq_boost_input_handler);
stop_kthreads:
while (i--)
kthread_stop(thread[i]);
return ret;
}
late_initcall(devfreq_boost_init);