/* * Samsung Exynos SoC series VIPx driver * * Copyright (c) 2018 Samsung Electronics Co., Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include "vipx-log.h" #include "vipx-system.h" #include "vipx-pm.h" #if defined(CONFIG_PM_DEVFREQ) #if defined(CONFIG_EXYNOS_VIPX_EXYNOS9610_A) static unsigned int cam_qos_table[] = { 700000, 690000, 680000, 670000, 660000, 650000, 640000 }; #endif #if defined(CONFIG_EXYNOS_VIPX_EXYNOS9610_R) static unsigned int cam_qos_table[] = { 690000, 680000, 670000, 660000, 650000, 640000 }; #endif static int __vipx_pm_qos_check_valid(struct vipx_pm *pm, int qos) { vipx_check(); if ((qos >= pm->qos_count) || (qos < 0)) { vipx_err("pm_qos level(%d) is invalid (L0 ~ L%d)\n", qos, pm->qos_count - 1); return -EINVAL; } else { return 0; } } int vipx_pm_qos_active(struct vipx_pm *pm) { vipx_check(); return pm_qos_request_active(&pm->cam_qos_req); } static void __vipx_pm_qos_set_default(struct vipx_pm *pm, int default_qos) { vipx_enter(); pm->default_qos = default_qos; vipx_info("default pm_qos level is set as L%d\n", pm->default_qos); vipx_leave(); } int vipx_pm_qos_set_default(struct vipx_pm *pm, int default_qos) { int ret; vipx_enter(); ret = __vipx_pm_qos_check_valid(pm, default_qos); if (ret) goto p_err; mutex_lock(&pm->lock); __vipx_pm_qos_set_default(pm, default_qos); mutex_unlock(&pm->lock); vipx_leave(); return 0; p_err: return ret; } static void __vipx_pm_qos_update(struct vipx_pm *pm, int request_qos) { vipx_enter(); if (vipx_pm_qos_active(pm) && (pm->current_qos != request_qos)) { pm_qos_update_request(&pm->cam_qos_req, pm->qos_table[request_qos]); vipx_dbg("pm_qos level is changed from L%d to L%d\n", pm->current_qos, request_qos); pm->current_qos = request_qos; } vipx_leave(); } int vipx_pm_qos_update(struct vipx_pm *pm, int request_qos) { int ret; vipx_enter(); ret = __vipx_pm_qos_check_valid(pm, request_qos); if (ret) goto p_err; mutex_lock(&pm->lock); __vipx_pm_qos_update(pm, request_qos); mutex_unlock(&pm->lock); vipx_leave(); return 0; p_err: return ret; } void vipx_pm_qos_suspend(struct vipx_pm *pm) { vipx_enter(); pm->resume_qos = pm->current_qos; vipx_pm_qos_update(pm, pm->qos_count - 1); vipx_leave(); } void vipx_pm_qos_resume(struct vipx_pm *pm) { vipx_enter(); vipx_pm_qos_update(pm, pm->resume_qos); pm->resume_qos = -1; vipx_leave(); } static int __vipx_pm_qos_add(struct vipx_pm *pm) { vipx_enter(); mutex_lock(&pm->lock); if (pm->default_qos < 0) __vipx_pm_qos_set_default(pm, 0); if (!vipx_pm_qos_active(pm)) { pm_qos_add_request(&pm->cam_qos_req, PM_QOS_CAM_THROUGHPUT, pm->qos_table[pm->default_qos]); pm->current_qos = pm->default_qos; vipx_info("The power of device is on(L%d)\n", pm->current_qos); } if (pm->dvfs) vipx_info("dvfs was enabled\n"); else vipx_info("dvfs was disabled\n"); mutex_unlock(&pm->lock); vipx_leave(); return 0; } static void __vipx_pm_qos_remove(struct vipx_pm *pm) { vipx_enter(); mutex_lock(&pm->lock); if (vipx_pm_qos_active(pm)) { pm_qos_remove_request(&pm->cam_qos_req); pm->current_qos = -1; vipx_info("The power of device is off\n"); } mutex_unlock(&pm->lock); vipx_leave(); } static int __vipx_pm_qos_init(struct vipx_pm *pm) { vipx_enter(); pm->qos_count = sizeof(cam_qos_table) / sizeof(unsigned int); pm->qos_table = cam_qos_table; pm->default_qos = -1; pm->resume_qos = -1; pm->current_qos = -1; vipx_leave(); return 0; } #else int vipx_pm_qos_active(struct vipx_pm *pm) { return 1; } int vipx_pm_qos_set_default(struct vipx_pm *pm, int default_qos) { return 0; } static void __vipx_pm_qos_update(struct vipx_pm *pm, int request_qos) { } int vipx_pm_qos_update(struct vipx_pm *pm, int request_qos) { return 0; } void vipx_pm_qos_suspend(struct vipx_pm *pm) { } void vipx_pm_qos_resume(struct vipx_pm *pm) { } static int __vipx_pm_qos_add(struct vipx_pm *pm) { vipx_info("dvfs was not supported\n"); return 0; } static void __vipx_pm_qos_remove(struct vipx_pm *pm) { } static int __vipx_pm_qos_init(struct vipx_pm *pm) { return 0; } #endif void vipx_pm_request_busy(struct vipx_pm *pm) { vipx_enter(); mutex_lock(&pm->lock); if (pm->dvfs) { if (++pm->active_count == 1) __vipx_pm_qos_update(pm, pm->default_qos); } mutex_unlock(&pm->lock); vipx_leave(); } void vipx_pm_request_idle(struct vipx_pm *pm) { vipx_enter(); mutex_lock(&pm->lock); if (pm->dvfs) { if (!pm->active_count) __vipx_pm_qos_update(pm, pm->qos_count - 1); else if (--pm->active_count == 0) __vipx_pm_qos_update(pm, pm->qos_count - 1); } mutex_unlock(&pm->lock); vipx_leave(); } int vipx_pm_open(struct vipx_pm *pm) { int ret; vipx_enter(); ret = __vipx_pm_qos_add(pm); if (ret) goto p_err; pm->active_count = 0; vipx_leave(); return 0; p_err: return ret; } void vipx_pm_close(struct vipx_pm *pm) { vipx_enter(); __vipx_pm_qos_remove(pm); vipx_leave(); } int vipx_pm_probe(struct vipx_system *sys) { struct vipx_pm *pm; vipx_enter(); pm = &sys->pm; pm->system = sys; __vipx_pm_qos_init(pm); #if defined(CONFIG_PM) pm_runtime_enable(sys->dev); #endif mutex_init(&pm->lock); pm->dvfs = true; vipx_leave(); return 0; } void vipx_pm_remove(struct vipx_pm *pm) { vipx_enter(); mutex_destroy(&pm->lock); #if defined(CONFIG_PM) pm_runtime_disable(pm->system->dev); #endif vipx_leave(); }