// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018-2021 Sultan Alsawaf . */ #define pr_fmt(fmt) "devfreq_boost: " fmt #include #include #include #include #include #include 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);