| // 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"); |