/* * (C) COPYRIGHT RockChip 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 licence. */ /** * @file rk.c * implementation of platform_specific_code on rk platforms, such as rk3328h. * * mali_device_driver(MDD) includes 2 parts : * .DP : platform_dependent_part : * located in /mali/platform// * .DP : common_part : * common part implemented by ARM. */ #define ENABLE_DEBUG_LOG #include "custom_log.h" #include #include #include #include #include #include #ifdef CONFIG_PM #include #endif #include #include #include #include #include #include #include #include #include #include #include "mali_kernel_common.h" #include "../../common/mali_osk_mali.h" /*---------------------------------------------------------------------------*/ u32 mali_group_error; /*---------------------------------------------------------------------------*/ #define RK3528_GPU_SD_SLP_HAST 0x10024 #define DEFAULT_UTILISATION_PERIOD_IN_MS (100) /* * rk_platform_context_of_mali_device. */ struct rk_context { /* mali device. */ struct device *dev; struct regmap *grf; /* is the GPU powered on? */ bool is_powered; /* debug only, the period in ms to count gpu_utilisation. */ unsigned int utilisation_period; }; struct rk_context *s_rk_context; /*---------------------------------------------------------------------------*/ #ifdef CONFIG_MALI_DEVFREQ static ssize_t utilisation_period_show(struct device *dev, struct device_attribute *attr, char *buf) { struct rk_context *platform = s_rk_context; ssize_t ret = 0; ret += snprintf(buf, PAGE_SIZE, "%u\n", platform->utilisation_period); return ret; } static ssize_t utilisation_period_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct rk_context *platform = s_rk_context; int ret = 0; ret = kstrtouint(buf, 0, &platform->utilisation_period); if (ret) { E("invalid input period : %s.", buf); return ret; } D("set utilisation_period to '%d'.", platform->utilisation_period); return count; } static ssize_t utilisation_show(struct device *dev, struct device_attribute *attr, char *buf) { struct rk_context *platform = s_rk_context; struct mali_device *mdev = dev_get_drvdata(dev); ssize_t ret = 0; unsigned long period_in_us = platform->utilisation_period * 1000; unsigned long total_time; unsigned long busy_time; unsigned long utilisation; mali_pm_reset_dvfs_utilisation(mdev); usleep_range(period_in_us, period_in_us + 100); mali_pm_get_dvfs_utilisation(mdev, &total_time, &busy_time); /* 'devfreq_dev_profile' instance registered to devfreq * also uses mali_pm_reset_dvfs_utilisation() * and mali_pm_get_dvfs_utilisation(). * So, it's better to disable GPU DVFS before reading this node. */ D("total_time : %lu, busy_time : %lu.", total_time, busy_time); utilisation = busy_time / (total_time / 100); ret += snprintf(buf, PAGE_SIZE, "%lu\n", utilisation); return ret; } static DEVICE_ATTR_RW(utilisation_period); static DEVICE_ATTR_RO(utilisation); #endif static int rk_context_create_sysfs_files(struct device *dev) { #ifdef CONFIG_MALI_DEVFREQ int ret; ret = device_create_file(dev, &dev_attr_utilisation_period); if (ret) { E("fail to create sysfs file 'utilisation_period'."); goto out; } ret = device_create_file(dev, &dev_attr_utilisation); if (ret) { E("fail to create sysfs file 'utilisation'."); goto remove_utilisation_period; } return 0; remove_utilisation_period: device_remove_file(dev, &dev_attr_utilisation_period); out: return ret; #else return 0; #endif } static void rk_context_remove_sysfs_files(struct device *dev) { #ifdef CONFIG_MALI_DEVFREQ device_remove_file(dev, &dev_attr_utilisation_period); device_remove_file(dev, &dev_attr_utilisation); #endif } /*---------------------------------------------------------------------------*/ /* * Init rk_platform_context of mali_device. */ static int rk_context_init(struct platform_device *pdev) { int ret = 0; struct device *dev = &pdev->dev; struct rk_context *platform; /* platform_context */ platform = kzalloc(sizeof(*platform), GFP_KERNEL); if (!platform) { E("no mem."); return _MALI_OSK_ERR_NOMEM; } platform->dev = dev; platform->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); if (IS_ERR(platform->grf)) platform->grf = NULL; platform->is_powered = false; platform->utilisation_period = DEFAULT_UTILISATION_PERIOD_IN_MS; ret = rk_context_create_sysfs_files(dev); if (ret) { E("fail to create sysfs files, ret = %d", ret); goto EXIT; } s_rk_context = platform; pm_runtime_set_autosuspend_delay(dev, 1000); pm_runtime_use_autosuspend(dev); pm_runtime_enable(dev); EXIT: return ret; } static void rk_context_deinit(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rk_context *platform = s_rk_context; pm_runtime_disable(dev); s_rk_context = NULL; rk_context_remove_sysfs_files(dev); if (platform) { platform->is_powered = false; platform->dev = NULL; kfree(platform); } } /*---------------------------------------------------------------------------*/ /* for devfreq cooling. */ #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_DEVFREQ_THERMAL) #define FALLBACK_STATIC_TEMPERATURE 55000 static u32 dynamic_coefficient; static u32 static_coefficient; static s32 ts[4]; static struct thermal_zone_device *gpu_tz; static struct ipa_power_model_data *model_data; /* Calculate gpu static power example for reference */ static unsigned long rk_model_static_power(struct devfreq *devfreq, unsigned long voltage) { int temperature, temp; int temp_squared, temp_cubed, temp_scaling_factor; const unsigned long voltage_cubed = (voltage * voltage * voltage) >> 10; unsigned long static_power; if (gpu_tz) { int ret; ret = gpu_tz->ops->get_temp(gpu_tz, &temperature); if (ret) { MALI_DEBUG_PRINT(2, ("fail to read temp: %d\n", ret)); temperature = FALLBACK_STATIC_TEMPERATURE; } } else { temperature = FALLBACK_STATIC_TEMPERATURE; } /* Calculate the temperature scaling factor. To be applied to the * voltage scaled power. */ temp = temperature / 1000; temp_squared = temp * temp; temp_cubed = temp_squared * temp; temp_scaling_factor = (ts[3] * temp_cubed) + (ts[2] * temp_squared) + (ts[1] * temp) + ts[0]; static_power = (((static_coefficient * voltage_cubed) >> 20) * temp_scaling_factor) / 1000000; return static_power; } /* Calculate gpu dynamic power example for reference */ static unsigned long rk_model_dynamic_power(struct devfreq *devfreq, unsigned long freq, unsigned long voltage) { /* The inputs: freq (f) is in Hz, and voltage (v) in mV. * The coefficient (c) is in mW/(MHz mV mV). * * This function calculates the dynamic power after this formula: * Pdyn (mW) = c (mW/(MHz*mV*mV)) * v (mV) * v (mV) * f (MHz) */ const unsigned long v2 = (voltage * voltage) / 1000; /* m*(V*V) */ const unsigned long f_mhz = freq / 1000000; /* MHz */ unsigned long dynamic_power; dynamic_power = (dynamic_coefficient * v2 * f_mhz) / 1000000; /* mW */ return dynamic_power; } static struct devfreq_cooling_power rk_cooling_ops = { .get_static_power = rk_model_static_power, .get_dynamic_power = rk_model_dynamic_power, }; static unsigned long mali_devfreq_get_static_power(struct devfreq *devfreq, unsigned long voltage) { return rockchip_ipa_get_static_power(model_data, voltage); } static int power_model_simple_init(struct platform_device *pdev) { struct device_node *power_model_node; const char *tz_name; u32 static_power, dynamic_power; u32 voltage, voltage_squared, voltage_cubed, frequency; if (of_find_compatible_node(pdev->dev.of_node, NULL, "simple-power-model")) { of_property_read_u32(pdev->dev.of_node, "dynamic-power-coefficient", (u32 *)&rk_cooling_ops.dyn_power_coeff); model_data = rockchip_ipa_power_model_init(&pdev->dev, "gpu_leakage"); if (IS_ERR_OR_NULL(model_data)) { model_data = NULL; dev_err(&pdev->dev, "failed to initialize power model\n"); } else if (model_data->dynamic_coefficient) { rk_cooling_ops.dyn_power_coeff = model_data->dynamic_coefficient; rk_cooling_ops.get_dynamic_power = NULL; rk_cooling_ops.get_static_power = mali_devfreq_get_static_power; } if (!rk_cooling_ops.dyn_power_coeff) { dev_err(&pdev->dev, "failed to get dynamic-coefficient\n"); return -EINVAL; } return 0; } power_model_node = of_get_child_by_name(pdev->dev.of_node, "power_model"); if (!power_model_node) { dev_err(&pdev->dev, "could not find power_model node\n"); return -ENODEV; } if (!of_device_is_compatible(power_model_node, "arm,mali-simple-power-model")) { dev_err(&pdev->dev, "power_model incompatible with simple power model\n"); return -ENODEV; } if (of_property_read_string(power_model_node, "thermal-zone", &tz_name)) { dev_err(&pdev->dev, "ts in power_model not available\n"); return -EINVAL; } gpu_tz = thermal_zone_get_zone_by_name(tz_name); if (IS_ERR(gpu_tz)) { pr_warn_ratelimited("Error getting gpu thermal zone '%s'(%ld), not yet ready?\n", tz_name, PTR_ERR(gpu_tz)); gpu_tz = NULL; } if (of_property_read_u32(power_model_node, "static-power", &static_power)) { dev_err(&pdev->dev, "static-power in power_model not available\n"); return -EINVAL; } if (of_property_read_u32(power_model_node, "dynamic-power", &dynamic_power)) { dev_err(&pdev->dev, "dynamic-power in power_model not available\n"); return -EINVAL; } if (of_property_read_u32(power_model_node, "voltage", &voltage)) { dev_err(&pdev->dev, "voltage in power_model not available\n"); return -EINVAL; } if (of_property_read_u32(power_model_node, "frequency", &frequency)) { dev_err(&pdev->dev, "frequency in power_model not available\n"); return -EINVAL; } voltage_squared = (voltage * voltage) / 1000; voltage_cubed = voltage * voltage * voltage; static_coefficient = (static_power << 20) / (voltage_cubed >> 10); dynamic_coefficient = (((dynamic_power * 1000) / voltage_squared) * 1000) / frequency; if (of_property_read_u32_array(power_model_node, "ts", (u32 *)ts, 4)) { dev_err(&pdev->dev, "ts in power_model not available\n"); return -EINVAL; } return 0; } #endif /*---------------------------------------------------------------------------*/ #ifdef CONFIG_PM static int rk_platform_enable_clk_gpu(struct device *dev) { int ret = 0; #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_HAVE_CLK) struct mali_device *mdev = dev_get_drvdata(dev); ret = clk_bulk_enable(mdev->num_clks, mdev->clks); #endif return ret; } static void rk_platform_disable_clk_gpu(struct device *dev) { #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_HAVE_CLK) struct mali_device *mdev = dev_get_drvdata(dev); clk_bulk_disable(mdev->num_clks, mdev->clks); #endif } static int rk_platform_enable_gpu_regulator(struct device *dev) { int ret = 0; #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_REGULATOR) struct mali_device *mdev = dev_get_drvdata(dev); if (mdev->regulator) ret = regulator_enable(mdev->regulator); #endif return ret; } static void rk_platform_disable_gpu_regulator(struct device *dev) { #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_REGULATOR) struct mali_device *mdev = dev_get_drvdata(dev); if (mdev->regulator) regulator_disable(mdev->regulator); #endif } static int rk_platform_power_on_gpu(struct device *dev) { struct rk_context *platform = s_rk_context; int ret = 0; if (!(platform->is_powered)) { ret = rk_platform_enable_clk_gpu(dev); if (ret) { E("fail to enable clk_gpu, ret : %d.", ret); goto fail_to_enable_clk; } ret = rk_platform_enable_gpu_regulator(dev); if (ret) { E("fail to enable vdd_gpu, ret : %d.", ret); goto fail_to_enable_regulator; } if (cpu_is_rk3528()) { #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_HAVE_CLK) struct mali_device *mdev = dev_get_drvdata(dev); clk_set_rate(mdev->clock, mdev->current_freq); #endif if (platform->grf) regmap_write(platform->grf, RK3528_GPU_SD_SLP_HAST, 0xffff0000); } platform->is_powered = true; } return 0; fail_to_enable_regulator: rk_platform_disable_clk_gpu(dev); fail_to_enable_clk: return ret; } static void rk_platform_power_off_gpu(struct device *dev) { struct rk_context *platform = s_rk_context; if (platform->is_powered) { if (cpu_is_rk3528()) { #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_HAVE_CLK) struct mali_device *mdev = dev_get_drvdata(dev); //use normal pll 200M for gpu when suspend clk_set_rate(mdev->clock, 200000000); #endif if (platform->grf) regmap_write(platform->grf, RK3528_GPU_SD_SLP_HAST, 0xfffffffd); } rk_platform_disable_clk_gpu(dev); rk_platform_disable_gpu_regulator(dev); platform->is_powered = false; } } int rk_platform_init_opp_table(struct device *dev) { return rockchip_init_opp_table(dev, NULL, "gpu_leakage", "mali"); } static int mali_runtime_suspend(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_runtime_suspend() called\n")); if (device->driver && device->driver->pm && device->driver->pm->runtime_suspend) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->runtime_suspend(device); } if (!ret) rk_platform_power_off_gpu(device); return ret; } static int mali_runtime_resume(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_runtime_resume() called\n")); rk_platform_power_on_gpu(device); if (device->driver && device->driver->pm && device->driver->pm->runtime_resume) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->runtime_resume(device); } return ret; } static int mali_runtime_idle(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_runtime_idle() called\n")); if (device->driver && device->driver->pm && device->driver->pm->runtime_idle) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->runtime_idle(device); if (ret) return ret; } return 0; } #endif static int mali_os_suspend(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_os_suspend() called\n")); if (device->driver && device->driver->pm && device->driver->pm->suspend) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->suspend(device); } if (!ret) rk_platform_power_off_gpu(device); return ret; } static int mali_os_resume(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_os_resume() called\n")); rk_platform_power_on_gpu(device); if (device->driver && device->driver->pm && device->driver->pm->resume) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->resume(device); } return ret; } static int mali_os_freeze(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_os_freeze() called\n")); if (device->driver && device->driver->pm && device->driver->pm->freeze) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->freeze(device); } return ret; } static int mali_os_thaw(struct device *device) { int ret = 0; MALI_DEBUG_PRINT(4, ("mali_os_thaw() called\n")); if (device->driver && device->driver->pm && device->driver->pm->thaw) { /* Need to notify Mali driver about this event */ ret = device->driver->pm->thaw(device); } return ret; } static const struct dev_pm_ops mali_gpu_device_type_pm_ops = { .suspend = mali_os_suspend, .resume = mali_os_resume, .freeze = mali_os_freeze, .thaw = mali_os_thaw, #ifdef CONFIG_PM .runtime_suspend = mali_runtime_suspend, .runtime_resume = mali_runtime_resume, .runtime_idle = mali_runtime_idle, #endif }; static const struct device_type mali_gpu_device_device_type = { .pm = &mali_gpu_device_type_pm_ops, }; /* * platform_specific_data of platform_device of mali_gpu. */ static const struct mali_gpu_device_data mali_gpu_data = { .shared_mem_size = 1024 * 1024 * 1024, /* 1GB */ .max_job_runtime = 60000, /* 60 seconds */ #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_DEVFREQ_THERMAL) .gpu_cooling_ops = &rk_cooling_ops, #endif }; static void mali_platform_device_add_config(struct platform_device *pdev) { pdev->name = MALI_GPU_NAME_UTGARD, pdev->id = 0; pdev->dev.type = &mali_gpu_device_device_type; pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask, pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); } /*---------------------------------------------------------------------------*/ /* platform_device_functions called by common_part. */ int mali_platform_device_init(struct platform_device *pdev) { int err = 0; mali_platform_device_add_config(pdev); D("to add platform_specific_data to platform_device_of_mali."); err = platform_device_add_data(pdev, &mali_gpu_data, sizeof(mali_gpu_data)); if (err) { E("fail to add platform_specific_data. err : %d.", err); goto add_data_failed; } err = rk_context_init(pdev); if (err) { E("fail to init rk_context. err : %d.", err); goto init_rk_context_failed; } #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_DEVFREQ_THERMAL) if (of_machine_is_compatible("rockchip,rk3036")) return 0; err = power_model_simple_init(pdev); if (err) { E("fail to init simple_power_model, err : %d.", err); goto init_power_model_failed; } #endif return 0; #if defined(CONFIG_MALI_DEVFREQ) && defined(CONFIG_DEVFREQ_THERMAL) init_power_model_failed: rk_context_deinit(pdev); #endif init_rk_context_failed: add_data_failed: return err; } void mali_platform_device_deinit(struct platform_device *pdev) { MALI_DEBUG_PRINT(4, ("mali_platform_device_unregister() called\n")); rk_context_deinit(pdev); }