Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
/*
 * (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 <mdd_src_dir>/mali/platform/<platform_name>/
 *	.DP : common_part :
 *		common part implemented by ARM.
 */

#define ENABLE_DEBUG_LOG
#include "custom_log.h"

#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/pm.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#ifdef CONFIG_PM
#include <linux/pm_runtime.h>
#endif
#include <linux/workqueue.h>
#include <linux/dma-mapping.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/rockchip/cpu.h>
#include <soc/rockchip/rockchip_opp_select.h>

#include <linux/mali/mali_utgard.h>
#include "mali_kernel_common.h"
#include "../../common/mali_osk_mali.h"

/*---------------------------------------------------------------------------*/

u32 mali_group_error;

/*---------------------------------------------------------------------------*/

#define DEFAULT_UTILISATION_PERIOD_IN_MS (100)

/*
 * rk_platform_context_of_mali_device.
 */
struct rk_context {
	/* mali device. */
	struct device *dev;
	/* 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->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 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;

	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;
}

/* 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;
}

struct devfreq_cooling_power rk_cooling_ops = {
	.get_static_power = rk_model_static_power,
	.get_dynamic_power = rk_model_dynamic_power,
};
#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);

	if (mdev->clock)
		ret = clk_enable(mdev->clock);
#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);

	if (mdev->clock)
		clk_disable(mdev->clock);
#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;
		}

		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) {
		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);
}