| /* |
| * Copyright (C) 2018 MediaTek Inc. |
| * |
| * 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. |
| * |
| * 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 http://www.gnu.org/licenses/gpl-2.0.html for more details. |
| */ |
| |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/regulator/consumer.h> |
| #include <mali_kbase.h> |
| #include "mali_kbase_config_platform.h" |
| #include <mali_kbase_defs.h> |
| |
| static struct platform_device *probe_gpu_core1_dev; |
| static struct platform_device *probe_gpu_core2_dev; |
| |
| static const struct of_device_id mtk_gpu_corex_of_ids[] = { |
| { .compatible = "mediatek,gpu_core1", .data = "1" }, |
| { .compatible = "mediatek,gpu_core2", .data = "2" }, |
| {} |
| }; |
| |
| static int mtk_gpu_corex_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| const struct of_device_id *match; |
| const char *tmp; |
| |
| match = of_match_device(mtk_gpu_corex_of_ids, dev); |
| if (!match) |
| return -ENODEV; |
| tmp = match->data; |
| if (*tmp == '1') |
| probe_gpu_core1_dev = pdev; |
| else |
| probe_gpu_core2_dev = pdev; |
| |
| pm_runtime_set_autosuspend_delay(&pdev->dev, 50); |
| pm_runtime_use_autosuspend(&pdev->dev); |
| pm_runtime_enable(&pdev->dev); |
| |
| return 0; |
| } |
| |
| static int mtk_gpu_corex_remove(struct platform_device *pdev) |
| { |
| pm_runtime_disable(&pdev->dev); |
| return 0; |
| } |
| |
| static struct platform_driver mtk_gpu_corex_driver = { |
| .probe = mtk_gpu_corex_probe, |
| .remove = mtk_gpu_corex_remove, |
| .driver = { |
| .name = "gpu_corex", |
| .of_match_table = mtk_gpu_corex_of_ids, |
| } |
| }; |
| |
| static int pm_callback_power_on(struct kbase_device *kbdev) |
| { |
| int error; |
| struct mfg_base *mfg = kbdev->platform_context; |
| |
| if (mfg->is_powered) { |
| dev_dbg(kbdev->dev, "mali_device is already powered\n"); |
| return 0; |
| } |
| |
| error = pm_runtime_get_sync(kbdev->dev); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "Power on core 0 failed (err: %d)\n", error); |
| return error; |
| } |
| |
| error = pm_runtime_get_sync(&mfg->gpu_core1_dev->dev); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "Power on core 1 failed (err: %d)\n", error); |
| return error; |
| } |
| |
| error = pm_runtime_get_sync(&mfg->gpu_core2_dev->dev); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "Power on core 2 failed (err: %d)\n", error); |
| return error; |
| } |
| |
| error = clk_enable(mfg->clk_main_parent); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "clk_main_parent clock enable failed (err: %d)\n", |
| error); |
| return error; |
| } |
| |
| error = clk_enable(mfg->clk_mux); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "clk_mux clock enable failed (err: %d)\n", error); |
| return error; |
| } |
| |
| error = clk_enable(mfg->subsys_mfg_cg); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "subsys_mfg_cg clock enable failed (err: %d)\n", error); |
| return error; |
| } |
| |
| mfg->is_powered = true; |
| |
| return 1; |
| } |
| |
| static void pm_callback_power_off(struct kbase_device *kbdev) |
| { |
| struct mfg_base *mfg = kbdev->platform_context; |
| int error; |
| |
| if (!mfg->is_powered) { |
| dev_dbg(kbdev->dev, "mali_device is already powered off\n"); |
| return; |
| } |
| |
| mfg->is_powered = false; |
| |
| clk_disable(mfg->subsys_mfg_cg); |
| |
| clk_disable(mfg->clk_mux); |
| |
| clk_disable(mfg->clk_main_parent); |
| |
| pm_runtime_mark_last_busy(&mfg->gpu_core2_dev->dev); |
| error = pm_runtime_put_autosuspend(&mfg->gpu_core2_dev->dev); |
| if (error < 0) |
| dev_err(kbdev->dev, |
| "Power off core 2 failed (err: %d)\n", error); |
| |
| pm_runtime_mark_last_busy(&mfg->gpu_core1_dev->dev); |
| error = pm_runtime_put_autosuspend(&mfg->gpu_core1_dev->dev); |
| if (error < 0) |
| dev_err(kbdev->dev, |
| "Power off core 1 failed (err: %d)\n", error); |
| |
| pm_runtime_mark_last_busy(kbdev->dev); |
| error = pm_runtime_put_autosuspend(kbdev->dev); |
| if (error < 0) |
| dev_err(kbdev->dev, |
| "Power off core 0 failed (err: %d)\n", error); |
| } |
| |
| static int kbase_device_runtime_init(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| static void kbase_device_runtime_disable(struct kbase_device *kbdev) |
| { |
| dev_dbg(kbdev->dev, "%s\n", __func__); |
| } |
| |
| static int pm_callback_runtime_on(struct kbase_device *kbdev) |
| { |
| struct mfg_base *mfg = kbdev->platform_context; |
| int error, i; |
| |
| for (i = 0; i < kbdev->nr_regulators; i++) { |
| error = regulator_enable(kbdev->regulators[i]); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "Power on reg %d failed error = %d\n", |
| i, error); |
| return error; |
| } |
| } |
| |
| error = clk_prepare(mfg->clk_main_parent); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "clk_main_parent clock prepare failed (err: %d)\n", |
| error); |
| return error; |
| } |
| |
| error = clk_prepare(mfg->clk_mux); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "clk_mux clock prepare failed (err: %d)\n", error); |
| return error; |
| } |
| |
| error = clk_prepare(mfg->subsys_mfg_cg); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "subsys_mfg_cg clock prepare failed (err: %d)\n", |
| error); |
| return error; |
| } |
| |
| return 0; |
| } |
| |
| static void pm_callback_runtime_off(struct kbase_device *kbdev) |
| { |
| struct mfg_base *mfg = kbdev->platform_context; |
| int error, i; |
| |
| clk_unprepare(mfg->subsys_mfg_cg); |
| |
| clk_unprepare(mfg->clk_mux); |
| |
| clk_unprepare(mfg->clk_main_parent); |
| |
| for (i = 0; i < kbdev->nr_regulators; i++) { |
| error = regulator_disable(kbdev->regulators[i]); |
| if (error < 0) { |
| dev_err(kbdev->dev, |
| "Power off reg %d failed error = %d\n", |
| i, error); |
| } |
| } |
| } |
| |
| static void pm_callback_resume(struct kbase_device *kbdev) |
| { |
| pm_callback_power_on(kbdev); |
| } |
| |
| static void pm_callback_suspend(struct kbase_device *kbdev) |
| { |
| pm_callback_power_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, |
| #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 */ |
| }; |
| |
| |
| int mali_mfgsys_init(struct kbase_device *kbdev, struct mfg_base *mfg) |
| { |
| int err = 0, i; |
| unsigned long volt; |
| |
| if (!probe_gpu_core1_dev || !probe_gpu_core2_dev) |
| return -EPROBE_DEFER; |
| |
| for (i = 0; i < kbdev->nr_regulators; i++) |
| if (kbdev->regulators[i] == NULL) |
| return -EINVAL; |
| |
| mfg->gpu_core1_dev = probe_gpu_core1_dev; |
| mfg->gpu_core2_dev = probe_gpu_core2_dev; |
| |
| mfg->clk_main_parent = devm_clk_get(kbdev->dev, "clk_main_parent"); |
| if (IS_ERR(mfg->clk_main_parent)) { |
| err = PTR_ERR(mfg->clk_main_parent); |
| dev_err(kbdev->dev, "devm_clk_get clk_main_parent failed\n"); |
| return err; |
| } |
| |
| mfg->clk_sub_parent = devm_clk_get(kbdev->dev, "clk_sub_parent"); |
| if (IS_ERR(mfg->clk_sub_parent)) { |
| err = PTR_ERR(mfg->clk_sub_parent); |
| dev_err(kbdev->dev, "devm_clk_get clk_sub_parent failed\n"); |
| return err; |
| } |
| |
| mfg->clk_mux = devm_clk_get(kbdev->dev, "clk_mux"); |
| if (IS_ERR(mfg->clk_mux)) { |
| err = PTR_ERR(mfg->clk_mux); |
| dev_err(kbdev->dev, "devm_clk_get clk_mux failed\n"); |
| return err; |
| } |
| |
| mfg->subsys_mfg_cg = devm_clk_get(kbdev->dev, "subsys_mfg_cg"); |
| if (IS_ERR(mfg->subsys_mfg_cg)) { |
| err = PTR_ERR(mfg->subsys_mfg_cg); |
| dev_err(kbdev->dev, "devm_clk_get subsys_mfg_cg failed\n"); |
| return err; |
| } |
| |
| for (i = 0; i < kbdev->nr_regulators; i++) { |
| volt = (0 == i) ? VGPU_MAX_VOLT : VSRAM_GPU_MAX_VOLT; |
| err = regulator_set_voltage(kbdev->regulators[i], |
| volt, volt + VOLT_TOL); |
| if (err < 0) { |
| dev_err(kbdev->dev, |
| "Regulator %d set voltage failed: %d\n", |
| i, err); |
| return err; |
| } |
| kbdev->current_voltages[i] = volt; |
| } |
| |
| mfg->is_powered = false; |
| |
| return 0; |
| } |
| |
| static void voltage_range_check(struct kbase_device *kbdev, |
| unsigned long *voltages) |
| { |
| if (voltages[1] - voltages[0] < MIN_VOLT_BIAS || |
| voltages[1] - voltages[0] > MAX_VOLT_BIAS) |
| voltages[1] = voltages[0] + MIN_VOLT_BIAS; |
| voltages[1] = clamp_t(unsigned long, voltages[1], VSRAM_GPU_MIN_VOLT, |
| VSRAM_GPU_MAX_VOLT); |
| } |
| |
| #ifdef CONFIG_REGULATOR |
| static bool get_step_volt(unsigned long *step_volt, unsigned long *target_volt, |
| int count, bool inc) |
| { |
| unsigned long regulator_min_volt; |
| unsigned long regulator_max_volt; |
| unsigned long current_bias; |
| long adjust_step; |
| int i; |
| |
| if (inc) { |
| current_bias = target_volt[1] - step_volt[0]; |
| adjust_step = MIN_VOLT_BIAS; |
| } else { |
| current_bias = step_volt[1] - target_volt[0]; |
| adjust_step = -MIN_VOLT_BIAS; |
| } |
| |
| for (i = 0; i < count; ++i) |
| if (step_volt[i] != target_volt[i]) |
| break; |
| |
| if (i == count) |
| return 0; |
| |
| for (i = 0; i < count; i++) { |
| if (i) { |
| regulator_min_volt = VSRAM_GPU_MIN_VOLT; |
| regulator_max_volt = VSRAM_GPU_MAX_VOLT; |
| } else { |
| regulator_min_volt = VGPU_MIN_VOLT; |
| regulator_max_volt = VGPU_MAX_VOLT; |
| } |
| |
| if (current_bias > MAX_VOLT_BIAS) { |
| step_volt[i] = clamp_val(step_volt[0] + adjust_step, |
| regulator_min_volt, |
| regulator_max_volt); |
| } else { |
| step_volt[i] = target_volt[i]; |
| } |
| } |
| return 1; |
| } |
| |
| static int set_voltages(struct kbase_device *kbdev, unsigned long *voltages, |
| bool inc) |
| { |
| unsigned long step_volt[BASE_MAX_NR_CLOCKS_REGULATORS]; |
| int first, step; |
| int i; |
| int err; |
| |
| for (i = 0; i < kbdev->nr_regulators; ++i) |
| step_volt[i] = kbdev->current_voltages[i]; |
| |
| if (inc) { |
| first = kbdev->nr_regulators - 1; |
| step = -1; |
| } else { |
| first = 0; |
| step = 1; |
| } |
| |
| while (get_step_volt(step_volt, voltages, kbdev->nr_regulators, inc)) { |
| for (i = first; i >= 0 && i < kbdev->nr_regulators; i += step) { |
| if (kbdev->current_voltages[i] == step_volt[i]) |
| continue; |
| |
| err = regulator_set_voltage(kbdev->regulators[i], |
| step_volt[i], |
| step_volt[i] + VOLT_TOL); |
| |
| if (err) { |
| dev_err(kbdev->dev, |
| "Failed to set reg %d voltage err:(%d)\n", |
| i, err); |
| return err; |
| } |
| |
| kbdev->current_voltages[i] = step_volt[i]; |
| } |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int set_frequency(struct kbase_device *kbdev, unsigned long freq) |
| { |
| int err; |
| struct mfg_base *mfg = kbdev->platform_context; |
| |
| if (kbdev->current_freqs[0] != freq) { |
| err = clk_set_parent(mfg->clk_mux, mfg->clk_sub_parent); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to select sub clock src\n"); |
| return err; |
| } |
| |
| err = clk_set_rate(kbdev->clocks[0], freq); |
| if (err) |
| return err; |
| |
| err = clk_set_parent(mfg->clk_mux, mfg->clk_main_parent); |
| if (err) { |
| dev_err(kbdev->dev, |
| "Failed to select main clock src\n"); |
| return err; |
| } |
| |
| kbdev->current_freqs[0] = freq; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| set_freqs_volts(struct kbase_device *kbdev, |
| unsigned long *freqs, unsigned long *volts) |
| { |
| int err; |
| |
| voltage_range_check(kbdev, volts); |
| |
| #ifdef CONFIG_REGULATOR |
| if (kbdev->current_voltages[0] < volts[0]) { |
| err = set_voltages(kbdev, volts, true); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to increase voltage\n"); |
| return err; |
| } |
| } |
| #endif |
| |
| err = set_frequency(kbdev, freqs[0]); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to set clock %lu\n", freqs[0]); |
| return err; |
| } |
| |
| #ifdef CONFIG_REGULATOR |
| if (kbdev->current_voltages[0] > volts[0]) { |
| err = set_voltages(kbdev, volts, false); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to decrease voltage\n"); |
| return err; |
| } |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int platform_init(struct kbase_device *kbdev) |
| { |
| int err; |
| struct mfg_base *mfg; |
| |
| mfg = kzalloc(sizeof(*mfg), GFP_KERNEL); |
| if (!mfg) |
| return -ENOMEM; |
| |
| err = mali_mfgsys_init(kbdev, mfg); |
| if (err) |
| goto platform_init_err; |
| |
| kbdev->platform_context = mfg; |
| pm_runtime_set_autosuspend_delay(kbdev->dev, 50); |
| pm_runtime_use_autosuspend(kbdev->dev); |
| pm_runtime_enable(kbdev->dev); |
| |
| err = clk_set_parent(mfg->clk_mux, mfg->clk_sub_parent); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to select sub clock src\n"); |
| goto platform_init_err; |
| } |
| |
| err = clk_set_rate(mfg->clk_main_parent, GPU_FREQ_KHZ_MAX * 1000); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to set clock %d kHz\n", |
| GPU_FREQ_KHZ_MAX); |
| goto platform_init_err; |
| } |
| |
| err = clk_set_parent(mfg->clk_mux, mfg->clk_main_parent); |
| if (err) { |
| dev_err(kbdev->dev, "Failed to select main clock src\n"); |
| goto platform_init_err; |
| } |
| |
| kbdev->devfreq_set_freqs_volts = set_freqs_volts; |
| |
| return 0; |
| |
| platform_init_err: |
| kfree(mfg); |
| return err; |
| } |
| |
| static void platform_term(struct kbase_device *kbdev) |
| { |
| struct mfg_base *mfg = kbdev->platform_context; |
| |
| kfree(mfg); |
| kbdev->platform_context = NULL; |
| pm_runtime_disable(kbdev->dev); |
| } |
| |
| struct kbase_platform_funcs_conf platform_funcs = { |
| .platform_init_func = platform_init, |
| .platform_term_func = platform_term |
| }; |
| |
| static int __init mtk_mfg_corex(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&mtk_gpu_corex_driver); |
| if (ret != 0) |
| pr_debug("%s: Failed to register GPU core driver", __func__); |
| |
| return ret; |
| } |
| |
| subsys_initcall(mtk_mfg_corex); |