// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include "vpu_cfg.h" #include #include #include "vpu_power.h" #include "vpu_debug.h" #include "vpu_algo.h" #include "vpu_cmd.h" #include "vpu_met.h" #include "vpu_hw.h" #include "apusys_power.h" static void vpu_pwr_off_locked(struct vpu_device *vd, int suspend); static void vpu_pwr_wake_lock(struct vpu_device *vd); static void vpu_pwr_wake_unlock(struct vpu_device *vd); /* Get APUSYS DVFS User ID from VPU ID */ static inline int adu(int id) { return (VPU0 + id); } static void vpu_pwr_off_timer(struct vpu_device *vd, unsigned long t) { mod_delayed_work(vpu_drv->wq, &vd->pw_off_work, msecs_to_jiffies(t)); } static void vpu_pwr_release(struct kref *ref) { struct vpu_device *vd = container_of(ref, struct vpu_device, pw_ref); unsigned long t = (unsigned long)vd->pw_off_latency; if (!t) /* t = 0: always on, return */ return; if (t == 1) /* t = 1: instant power off */ t = 0; vpu_pwr_debug("%s: vpu%d: vpu_pwr_off_timer: %ld ms\n", __func__, vd->id, t); vpu_pwr_wake_unlock(vd); vpu_pwr_off_timer(vd, t /* ms */); } static void vpu_pwr_param(struct vpu_device *vd, uint8_t boost) { uint8_t opp; if (boost == VPU_PWR_NO_BOOST) return; opp = apusys_boost_value_to_opp(adu(vd->id), boost); vpu_pwr_debug("%s: vpu%d: boost: %d, opp: %d\n", __func__, vd->id, boost, opp); apu_device_set_opp(adu(vd->id), opp); atomic_set(&vd->pw_boost, boost); } /** * vpu_pwr_get_locked() - acquire vpu power and increase power reference * @vd: vpu device * @boost: boost value: 0~100, given from vpu_request * * 1. vd->lock and vpu_cmd_lock() must be locked before calling this function * 2. Must paired with vpu_pwr_put_locked() */ int vpu_pwr_get_locked(struct vpu_device *vd, uint8_t boost) { int ret = 0; if (vd->state == VS_REMOVING) { pr_info("%s: vpu%d is going to be removed\n", __func__, vd->id); return -ENODEV; } cancel_delayed_work(&vd->pw_off_work); if (kref_get_unless_zero(&vd->pw_ref)) { vpu_pwr_debug("%s: vpu%d: ref: %d, boost: %d\n", __func__, vd->id, kref_read(&vd->pw_ref), boost); goto out; } if (vd->state == VS_DOWN) { vpu_pwr_debug("%s: vpu%d: apu_device_power_on\n", __func__, vd->id); ret = apu_device_power_on(adu(vd->id)); if (ret) { vpu_pwr_debug("%s: vpu%d: apu_device_power_on: failed: %d\n", __func__, vd->id, ret); goto err; } vd->state = VS_UP; } ret = vpu_kbuf_alloc(vd); if (ret) goto err; kref_init(&vd->pw_ref); atomic_set(&vd->pw_boost, VPU_PWR_NO_BOOST); vpu_pwr_wake_lock(vd); vpu_pwr_debug("%s: vpu%d: ref: 1\n", __func__, vd->id); out: vpu_pwr_param(vd, boost); err: return ret; } /** * vpu_pwr_put_locked() - decrease power reference * @vd: vpu device * @boost: boost value to be restored * * 1. Must paired with vpu_pwr_get_locked() * 2. There's no need to acquire vd->lock or vpu_cmd_lock() * since the power off is done asynchronously by * vpu_pwr_off() when vd->pw_ref reaches zero, * and kref_put() itself is atomic. */ void vpu_pwr_put_locked(struct vpu_device *vd, uint8_t boost) { if (!kref_read(&vd->pw_ref)) { vpu_pwr_debug("%s: vpu%d: ref is already zero\n", __func__, vd->id); return; } vpu_pwr_debug("%s: vpu%d: ref: %d--\n", __func__, vd->id, vpu_pwr_cnt(vd)); vpu_pwr_param(vd, boost); kref_put(&vd->pw_ref, vpu_pwr_release); } /** * vpu_pwr_up_locked() - unconditionally power up VPU * @vd: vpu device * @boost: boost value: 0~100, given from vpu_request * @off_timer: set power off timer (us), 0: always on * * vd->lock and vpu_cmd_lock() must be locked before calling this function */ int vpu_pwr_up_locked(struct vpu_device *vd, uint8_t boost, uint32_t off_timer) { int ret; uint64_t pol; ret = vpu_pwr_get_locked(vd, boost); if (ret) return ret; if (off_timer) { pol = vd->pw_off_latency; vd->pw_off_latency = off_timer; /* No need to restore boost for unconditionally power up */ vpu_pwr_put_locked_nb(vd); vd->pw_off_latency = pol; } vpu_pwr_debug("%s: powered up %d\n", __func__, ret); return 0; } /** * vpu_pwr_down_ext_locked() - unconditionally power down VPU * @vd: vpu device * @suspend: 0: normal power off flow, 1: suspend power off flow * vd->lock and vpu_cmd_lock() must be locked before calling this function */ static void vpu_pwr_down_ext_locked(struct vpu_device *vd, int suspend) { if (kref_read(&vd->pw_ref)) vpu_pwr_wake_unlock(vd); refcount_set(&vd->pw_ref.refcount, 0); cancel_delayed_work(&vd->pw_off_work); vpu_pwr_off_locked(vd, suspend); vpu_pwr_debug("%s: vpu%d: powered down(%d)\n", __func__, vd->id, suspend); } /** * vpu_pwr_down_locked() - unconditionally power down VPU * @vd: vpu device * * vd->lock and vpu_cmd_lock() must be locked before calling this function */ void vpu_pwr_down_locked(struct vpu_device *vd) { vpu_pwr_down_ext_locked(vd, /* suspend */ 0); } /** * vpu_pwr_suspend_locked() - power down VPU when suspend * @vd: vpu device * * vd->lock and vpu_cmd_lock() must be locked before calling this function */ void vpu_pwr_suspend_locked(struct vpu_device *vd) { vpu_pwr_down_ext_locked(vd, /* suspend */ 1); } /** * vpu_pwr_up() - unconditionally power up VPU * @vd: vpu device * @boost: boost value: 0~100, given from vpu_request * @off_timer: set power off timer (us), 0: always on * */ int vpu_pwr_up(struct vpu_device *vd, uint8_t boost, uint32_t off_timer) { int ret; vpu_cmd_lock_all(vd); ret = vpu_pwr_up_locked(vd, boost, off_timer); vpu_cmd_unlock_all(vd); return ret; } /** * vpu_pwr_down() - unconditionally power down VPU * @vd: vpu device * */ void vpu_pwr_down(struct vpu_device *vd) { vpu_cmd_lock_all(vd); vpu_pwr_down_locked(vd); vpu_cmd_unlock_all(vd); } static void vpu_pwr_off_locked(struct vpu_device *vd, int suspend) { int ret; int adu_id = adu(vd->id); struct vpu_sys_ops *sops = vd_sops(vd); vpu_cmd_clear(vd); vpu_kbuf_free(vd); vpu_met_pm_put(vd); if (vd->state <= VS_DOWN) { vpu_pwr_debug("%s: vpu%d: already off: %d\n", __func__, vd->id, vd->state); return; } if (sops->xos_wait_idle && sops->xos_wait_idle(vd)) pr_info("%s: vpu%d: not in idle state\n", __func__, vd->id); vpu_pwr_debug("%s: vpu%d: apu_device_power_suspend(%d, %d)\n", __func__, vd->id, adu_id, suspend); ret = apu_device_power_suspend(adu_id, suspend); if (ret) vpu_pwr_debug("%s: vpu%d: apu_device_power_suspend(%d, %d): failed: %d\n", __func__, vd->id, adu_id, suspend, ret); vd->state = VS_DOWN; /* Unlock xos_lock, in case the the lock is still hold by us. * Other xos_lock requesters will get -EAGAIN at VS_DOWN state. */ if (sops->xos_unlock) sops->xos_unlock(vd); } /** * vpu_pwr_off() - worker function of delayed power off * @vd: vpu device * */ static void vpu_pwr_off(struct work_struct *work) { struct vpu_device *vd = container_of(work, struct vpu_device, pw_off_work.work); vpu_pwr_down(vd); wake_up_interruptible(&vd->pw_wait); } static void vpu_pwr_wake_lock(struct vpu_device *vd) { #if IS_ENABLED(CONFIG_PM_SLEEP) __pm_stay_awake(vd->ws); #endif } static void vpu_pwr_wake_unlock(struct vpu_device *vd) { #if IS_ENABLED(CONFIG_PM_SLEEP) __pm_relax(vd->ws); #endif } static void vpu_pwr_wake_init(struct vpu_device *vd) { #if IS_ENABLED(CONFIG_PM_SLEEP) vd->ws = wakeup_source_register(NULL, vd->name); #endif } static void vpu_pwr_wake_exit(struct vpu_device *vd) { #if IS_ENABLED(CONFIG_PM_SLEEP) wakeup_source_unregister(vd->ws); #endif } int vpu_init_dev_pwr(struct platform_device *pdev, struct vpu_device *vd) { int ret = 0; INIT_DELAYED_WORK(&vd->pw_off_work, vpu_pwr_off); init_waitqueue_head(&vd->pw_wait); vd->pw_off_latency = vd_cfg(vd)->pw_off_latency_ms; refcount_set(&vd->pw_ref.refcount, 0); vpu_drv_debug("%s: vpu%d\n", __func__, vd->id); /* initialize APU power management data, and register to APU power */ vd->pd.dev_type = adu(vd->id); vd->pd.dev_core = vd->id; ret = apu_power_device_register(adu(vd->id), pdev); if (ret) dev_info(&pdev->dev, "apu_power_device_register: %d\n", ret); vpu_pwr_wake_init(vd); return ret; } int vpu_init_dev_pwr_rv(struct platform_device *pdev, struct vpu_device *vd) { return 0; } void vpu_exit_dev_pwr(struct platform_device *pdev, struct vpu_device *vd) { if (flush_delayed_work(&vd->pw_off_work)) { vpu_drv_debug("%s: vpu%d: pw_off_work done\n", __func__, vd->id); } else if (kref_read(&vd->pw_ref) > 0) { vpu_pwr_off_locked(vd, 0); vpu_drv_debug("%s: vpu%d: forced power down\n", __func__, vd->id); } vpu_pwr_wake_exit(vd); apu_power_device_unregister(adu(vd->id)); }