| /* |
| * 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 <linux/pm_runtime.h> |
| |
| #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(); |
| } |