| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| * Author: Anthony Huang <anthony.huang@mediatek.com> |
| */ |
| |
| #include <linux/module.h> |
| #include "mmqos-mtk.h" |
| |
| #define MULTIPLY_W_DRAM_WEIGHT(value) ((value)*6/5) |
| |
| struct mmqos_hrt *mmqos_hrt; |
| |
| s32 mtk_mmqos_get_avail_hrt_bw(enum hrt_type type) |
| { |
| u32 i, used_bw = 0; |
| |
| if (!mmqos_hrt) |
| return 0xFFFF; |
| |
| for (i = 0; i < HRT_TYPE_NUM; i++) { |
| if (mmqos_hrt->hrt_bw[i] != type) |
| used_bw += mmqos_hrt->hrt_bw[i]; |
| } |
| |
| if (mmqos_hrt->cam_max_bw) |
| used_bw = used_bw - mmqos_hrt->hrt_bw[HRT_CAM] |
| + mmqos_hrt->cam_max_bw; |
| |
| return (mmqos_hrt->hrt_total_bw - used_bw); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_get_avail_hrt_bw); |
| |
| |
| s32 mtk_mmqos_register_bw_throttle_notifier(struct notifier_block *nb) |
| { |
| if (!nb || !mmqos_hrt) |
| return -EINVAL; |
| return blocking_notifier_chain_register( |
| &mmqos_hrt->hrt_bw_throttle_notifier, |
| nb); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_register_bw_throttle_notifier); |
| |
| s32 mtk_mmqos_unregister_bw_throttle_notifier(struct notifier_block *nb) |
| { |
| if (!nb || !mmqos_hrt) |
| return -EINVAL; |
| return blocking_notifier_chain_unregister( |
| &mmqos_hrt->hrt_bw_throttle_notifier, |
| nb); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_unregister_bw_throttle_notifier); |
| |
| void mtk_mmqos_wait_throttle_done(void) |
| { |
| u32 wait_result; |
| |
| if (!mmqos_hrt) |
| return; |
| |
| if (atomic_read(&mmqos_hrt->lock_count) > 0) { |
| pr_notice("begin to blocking for cam_max_bw=%d\n", |
| mmqos_hrt->cam_max_bw); |
| wait_result = wait_event_timeout(mmqos_hrt->hrt_wait, |
| atomic_read(&mmqos_hrt->lock_count) == 0, |
| msecs_to_jiffies(200)); |
| pr_notice("blocking wait_result=%d\n", wait_result); |
| } |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_wait_throttle_done); |
| |
| s32 mtk_mmqos_set_hrt_bw(enum hrt_type type, u32 bw) |
| { |
| if (type >= HRT_TYPE_NUM) { |
| pr_notice("%s: wrong type:%d\n", __func__, type); |
| return -EINVAL; |
| } |
| |
| if (!mmqos_hrt) |
| return -EINVAL; |
| |
| mmqos_hrt->hrt_bw[type] = bw; |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_set_hrt_bw); |
| |
| static void notify_bw_throttle(u32 bw) |
| { |
| u64 start_jiffies = jiffies; |
| |
| blocking_notifier_call_chain(&mmqos_hrt->hrt_bw_throttle_notifier, |
| (bw > 0)?BW_THROTTLE_START:BW_THROTTLE_END, NULL); |
| |
| pr_notice("%s: notify_time=%u\n", __func__, |
| jiffies_to_msecs(jiffies-start_jiffies)); |
| } |
| |
| static void set_camera_max_bw(u32 bw) |
| { |
| mmqos_hrt->cam_max_bw = bw; |
| pr_notice("%s: %d\n", __func__, bw); |
| |
| if (mmqos_hrt->blocking) { |
| atomic_inc(&mmqos_hrt->lock_count); |
| pr_notice("%s: increase lock_count=%d\n", __func__, |
| atomic_read(&mmqos_hrt->lock_count)); |
| } |
| notify_bw_throttle(bw); |
| |
| if (mmqos_hrt->blocking) { |
| atomic_dec(&mmqos_hrt->lock_count); |
| wake_up(&mmqos_hrt->hrt_wait); |
| pr_notice("%s: decrease lock_count=%d\n", __func__, |
| atomic_read(&mmqos_hrt->lock_count)); |
| } |
| } |
| |
| static void delay_work_handler(struct work_struct *work) |
| { |
| mutex_lock(&mmqos_hrt->blocking_lock); |
| set_camera_max_bw(mmqos_hrt->cam_occu_bw); |
| mutex_unlock(&mmqos_hrt->blocking_lock); |
| } |
| |
| static ssize_t camera_max_bw_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| s32 ret; |
| u32 bw = 0; |
| |
| ret = kstrtoint(buf, 10, &bw); |
| if (ret) { |
| dev_notice(dev, "wrong camera max bw string:%d\n", ret); |
| return ret; |
| } |
| |
| cancel_delayed_work_sync(&mmqos_hrt->work); |
| mmqos_hrt->cam_occu_bw = MULTIPLY_W_DRAM_WEIGHT(bw); |
| mutex_lock(&mmqos_hrt->blocking_lock); |
| if (mmqos_hrt->cam_occu_bw < mmqos_hrt->cam_max_bw) { |
| mmqos_hrt->blocking = false; |
| schedule_delayed_work(&mmqos_hrt->work, 2 * HZ); |
| } else { |
| mmqos_hrt->blocking = true; |
| schedule_delayed_work(&mmqos_hrt->work, 0); |
| } |
| mutex_unlock(&mmqos_hrt->blocking_lock); |
| |
| return count; |
| } |
| static DEVICE_ATTR_WO(camera_max_bw); |
| |
| void mtk_mmqos_init_hrt(struct mmqos_hrt *hrt) |
| { |
| if (!hrt) |
| return; |
| mmqos_hrt = hrt; |
| atomic_set(&mmqos_hrt->lock_count, 0); |
| INIT_DELAYED_WORK(&mmqos_hrt->work, delay_work_handler); |
| BLOCKING_INIT_NOTIFIER_HEAD(&mmqos_hrt->hrt_bw_throttle_notifier); |
| mutex_init(&mmqos_hrt->blocking_lock); |
| init_waitqueue_head(&mmqos_hrt->hrt_wait); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_init_hrt); |
| |
| static struct attribute *mmqos_hrt_sysfs_attrs[] = { |
| &dev_attr_camera_max_bw.attr, |
| NULL |
| }; |
| |
| static struct attribute_group mmqos_hrt_sysfs_attr_group = { |
| .name = "mmqos_hrt", |
| .attrs = mmqos_hrt_sysfs_attrs |
| }; |
| |
| int mtk_mmqos_register_hrt_sysfs(struct device *dev) |
| { |
| return sysfs_create_group(&dev->kobj, &mmqos_hrt_sysfs_attr_group); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_register_hrt_sysfs); |
| |
| void mtk_mmqos_unregister_hrt_sysfs(struct device *dev) |
| { |
| sysfs_remove_group(&dev->kobj, &mmqos_hrt_sysfs_attr_group); |
| } |
| EXPORT_SYMBOL_GPL(mtk_mmqos_unregister_hrt_sysfs); |
| |
| |
| MODULE_LICENSE("GPL v2"); |