| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2015, 2017-2022 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU license. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| */ |
| |
| #include <mali_kbase.h> |
| #include <mali_kbase_defs.h> |
| #include <device/mali_kbase_device.h> |
| |
| #include <linux/pm_runtime.h> |
| #include <linux/reset.h> |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/delay.h> |
| #include <linux/regulator/consumer.h> |
| |
| #include "mali_kbase_config_platform.h" |
| |
| |
| static struct reset_control **resets; |
| static int nr_resets; |
| |
| static int resets_init(struct kbase_device *kbdev) |
| { |
| struct device_node *np; |
| int i; |
| int err = 0; |
| |
| np = kbdev->dev->of_node; |
| |
| nr_resets = of_count_phandle_with_args(np, "resets", "#reset-cells"); |
| if (nr_resets <= 0) { |
| dev_err(kbdev->dev, "Failed to get GPU resets from dtb\n"); |
| return nr_resets; |
| } |
| |
| resets = devm_kcalloc(kbdev->dev, nr_resets, sizeof(*resets), |
| GFP_KERNEL); |
| if (!resets) |
| return -ENOMEM; |
| |
| for (i = 0; i < nr_resets; ++i) { |
| resets[i] = devm_reset_control_get_exclusive_by_index( |
| kbdev->dev, i); |
| if (IS_ERR(resets[i])) { |
| err = PTR_ERR(resets[i]); |
| nr_resets = i; |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| static int pm_callback_soft_reset(struct kbase_device *kbdev) |
| { |
| int ret, i; |
| |
| if (!resets) { |
| ret = resets_init(kbdev); |
| if (ret) |
| return ret; |
| } |
| |
| for (i = 0; i < nr_resets; ++i) |
| reset_control_assert(resets[i]); |
| |
| udelay(10); |
| |
| for (i = 0; i < nr_resets; ++i) |
| reset_control_deassert(resets[i]); |
| |
| udelay(10); |
| |
| /* Override Power Management Settings, values from manufacturer's defaults */ |
| kbase_reg_write(kbdev, GPU_CONTROL_REG(PWR_KEY), 0x2968A819); |
| kbase_reg_write(kbdev, GPU_CONTROL_REG(PWR_OVERRIDE1), |
| 0xfff | (0x20 << 16)); |
| |
| /* |
| * RESET_COMPLETED interrupt will be raised, so continue with |
| * the normal soft reset procedure |
| */ |
| return 0; |
| } |
| |
| static void enable_gpu_power_control(struct kbase_device *kbdev) |
| { |
| unsigned int i; |
| |
| #if defined(CONFIG_REGULATOR) |
| for (i = 0; i < kbdev->nr_regulators; i++) { |
| if (WARN_ON(kbdev->regulators[i] == NULL)) |
| ; |
| else if (!regulator_is_enabled(kbdev->regulators[i])) |
| WARN_ON(regulator_enable(kbdev->regulators[i])); |
| } |
| #endif |
| |
| for (i = 0; i < kbdev->nr_clocks; i++) { |
| if (WARN_ON(kbdev->clocks[i] == NULL)) |
| ; |
| else if (!__clk_is_enabled(kbdev->clocks[i])) |
| WARN_ON(clk_prepare_enable(kbdev->clocks[i])); |
| } |
| } |
| |
| static void disable_gpu_power_control(struct kbase_device *kbdev) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < kbdev->nr_clocks; i++) { |
| if (WARN_ON(kbdev->clocks[i] == NULL)) |
| ; |
| else if (__clk_is_enabled(kbdev->clocks[i])) { |
| clk_disable_unprepare(kbdev->clocks[i]); |
| WARN_ON(__clk_is_enabled(kbdev->clocks[i])); |
| } |
| } |
| |
| #if defined(CONFIG_REGULATOR) |
| for (i = 0; i < kbdev->nr_regulators; i++) { |
| if (WARN_ON(kbdev->regulators[i] == NULL)) |
| ; |
| else if (regulator_is_enabled(kbdev->regulators[i])) |
| WARN_ON(regulator_disable(kbdev->regulators[i])); |
| } |
| #endif |
| } |
| |
| static int pm_callback_power_on(struct kbase_device *kbdev) |
| { |
| int ret = 1; /* Assume GPU has been powered off */ |
| int error; |
| |
| dev_dbg(kbdev->dev, "%s %pK\n", __func__, (void *)kbdev->dev->pm_domain); |
| |
| #ifdef KBASE_PM_RUNTIME |
| error = pm_runtime_get_sync(kbdev->dev); |
| if (error == 1) { |
| /* |
| * Let core know that the chip has not been |
| * powered off, so we can save on re-initialization. |
| */ |
| ret = 0; |
| } |
| dev_dbg(kbdev->dev, "pm_runtime_get_sync returned %d\n", error); |
| #else |
| enable_gpu_power_control(kbdev); |
| #endif |
| |
| return ret; |
| } |
| |
| static void pm_callback_power_off(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| #ifdef KBASE_PM_RUNTIME |
| pm_runtime_mark_last_busy(kbdev->dev); |
| pm_runtime_put_autosuspend(kbdev->dev); |
| #else |
| /* Power down the GPU immediately as runtime PM is disabled */ |
| disable_gpu_power_control(kbdev); |
| #endif |
| } |
| |
| #ifdef KBASE_PM_RUNTIME |
| static int kbase_device_runtime_init(struct kbase_device *kbdev) |
| { |
| int ret = 0; |
| |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| pm_runtime_set_autosuspend_delay(kbdev->dev, AUTO_SUSPEND_DELAY); |
| pm_runtime_use_autosuspend(kbdev->dev); |
| |
| pm_runtime_set_active(kbdev->dev); |
| pm_runtime_enable(kbdev->dev); |
| |
| if (!pm_runtime_enabled(kbdev->dev)) { |
| dev_warn(kbdev->dev, "pm_runtime not enabled"); |
| ret = -EINVAL; |
| } else if (atomic_read(&kbdev->dev->power.usage_count)) { |
| dev_warn(kbdev->dev, "%s: Device runtime usage count unexpectedly non zero %d", |
| __func__, atomic_read(&kbdev->dev->power.usage_count)); |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static void kbase_device_runtime_disable(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| if (atomic_read(&kbdev->dev->power.usage_count)) |
| dev_warn(kbdev->dev, "%s: Device runtime usage count unexpectedly non zero %d", |
| __func__, atomic_read(&kbdev->dev->power.usage_count)); |
| |
| pm_runtime_disable(kbdev->dev); |
| } |
| #endif /* KBASE_PM_RUNTIME */ |
| |
| static int pm_callback_runtime_on(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| enable_gpu_power_control(kbdev); |
| return 0; |
| } |
| |
| static void pm_callback_runtime_off(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| disable_gpu_power_control(kbdev); |
| } |
| |
| static void pm_callback_resume(struct kbase_device *kbdev) |
| { |
| int ret = pm_callback_runtime_on(kbdev); |
| |
| WARN_ON(ret); |
| } |
| |
| static void pm_callback_suspend(struct kbase_device *kbdev) |
| { |
| pm_callback_runtime_off(kbdev); |
| } |
| |
| struct kbase_pm_callback_conf pm_callbacks = { |
| .power_on_callback = pm_callback_power_on, |
| .power_off_callback = pm_callback_power_off, |
| .power_suspend_callback = pm_callback_suspend, |
| .power_resume_callback = pm_callback_resume, |
| .soft_reset_callback = pm_callback_soft_reset, |
| #ifdef KBASE_PM_RUNTIME |
| .power_runtime_init_callback = kbase_device_runtime_init, |
| .power_runtime_term_callback = kbase_device_runtime_disable, |
| .power_runtime_on_callback = pm_callback_runtime_on, |
| .power_runtime_off_callback = pm_callback_runtime_off, |
| #else /* KBASE_PM_RUNTIME */ |
| .power_runtime_init_callback = NULL, |
| .power_runtime_term_callback = NULL, |
| .power_runtime_on_callback = NULL, |
| .power_runtime_off_callback = NULL, |
| #endif /* KBASE_PM_RUNTIME */ |
| }; |