/**
 * df_rgx_is_valid_freq() - Determines if We are about to use
 * a valid frequency.
 * @freq: frequency to be validated.
 *
 * Function return value: 1 if Valid 0 if not.
 */
unsigned int df_rgx_is_valid_freq(unsigned long int freq)
{
	unsigned int valid = 0;
	int i;
	int aSize = NUMBER_OF_LEVELS;

	if(!is_tng_a0)
		aSize = NUMBER_OF_LEVELS_B0;

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s freq: %d\n", __func__, freq);

	for(i = 0; i < aSize; i++)
	{
		if(freq == aAvailableStateFreq[i].freq)
		{
			valid = 1;
			break;
		}
	}

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s valid: %d\n", __func__,
			valid);

	return valid;
}
/**
 * dev_freq_add_attributes_to_sysfs() - Creates all the sysfs entries
 * for the specified platform device.
 * @dev: platform device.
 */
int dev_freq_add_attributes_to_sysfs(struct device *device)
{
	int error = 0;
	int i = 0;

	if (!device)
		return -1;

	for (i = 0; devfreq_attrs[i].attr.mode != 0; i++) {
		error = device_create_file(device, &devfreq_attrs[i]);
		if (error) {
			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: could not"
				"create device file error = %0x\n",
				__func__, error);
			break;
		}
	}

	/*Were all the files created?*/
	if (devfreq_attrs[i].attr.mode != 0) {
		int j = 0;
		for (j = 0; j < i; j++)
			device_remove_file(device, &devfreq_attrs[i]);
	}

	return error;
}
/**
 * store_turbo_profile() - Write function for sysfs
 * sys/devices/platform/dfrgx/custom_turbo_profile.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to.
 */
static ssize_t store_turbo_profile(struct device *dev,
			struct device_attribute *attr,
			const char *buf, size_t count)
{
	struct busfreq_data *bfdata = dev_get_drvdata(dev);
	int ret = -EINVAL;
	int low_th = 0, high_th = 0;

	ret = sscanf(buf, "%d %d", &low_th, &high_th);

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: count = %zu, ret = %u\n",
				__func__, count, ret);
	if (ret != 2)
		goto out;

	if (low_th > high_th) {
		ret = 0;
		goto out;
	}

	a_governor_profile[DFRGX_TURBO_PROFILE_CUSTOM].util_th_low = low_th;
	a_governor_profile[DFRGX_TURBO_PROFILE_CUSTOM].util_th_high = high_th;

	bfdata->g_dfrgx_data.g_profile_index = DFRGX_TURBO_PROFILE_CUSTOM;
	ret = count;

out:
	return ret;
}
/**
 * df_rgx_bus_exit() - An optional callback that is called when devfreq is
 * removing the devfreq object due to error or from devfreq_remove_device()
 * call. If the user has registered devfreq->nb at a notifier-head, this is
 * the time to unregister it.
 */
static void df_rgx_bus_exit(struct device *dev)
{
	struct busfreq_data *bfdata = dev_get_drvdata(dev);
	(void) bfdata;

	DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);

	/*  devfreq_unregister_opp_notifier(dev, bfdata->devfreq); */
}
/**
 * show_turbo_profile() - Read function for sysfs
 * sys/devices/platform/dfrgx/custom_turbo_profile.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to.
 */
static ssize_t show_turbo_profile(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s\n",
				__func__);

	return scnprintf(buf, PAGE_SIZE, "%d %d\n",
		a_governor_profile[DFRGX_TURBO_PROFILE_CUSTOM].util_th_low,
		a_governor_profile[DFRGX_TURBO_PROFILE_CUSTOM].util_th_high);
}
/**
 * show_profiling_state() - Read function for sysfs
 * sys/devices/platform/dfrgx/profiling_state.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to.
 */
static ssize_t show_profiling_state(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	struct busfreq_data *bfdata = dev_get_drvdata(dev);
	int profiling_state = dfrgx_profiling_is_enabled(&bfdata->g_dfrgx_data);

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: value = %d\n",
				__func__, profiling_state);

	return scnprintf(buf, PAGE_SIZE, "%d\n", profiling_state);
}
/**
 * reset_profiling_stats() - Command function to reset stats.
 * Use cat sysfs sys/devices/platform/dfrgx/profiling_reset_stats
 * to perform this action.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to. Ignored.
 */
static ssize_t reset_profiling_stats(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s\n",
				__func__);

	gpu_profiling_records_restart();

	return 0;
}
/**
 * show_profiling_stats() - Read function for sysfs
 * sys/devices/platform/dfrgx/profiling_show_stats.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to.
 */
static ssize_t show_profiling_stats(struct device *dev,
			struct device_attribute *attr,
			char *buf)
{
	int ret = 0;
	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s\n",
				__func__);

	ret = gpu_profiling_records_show(buf);

	return ret;
}
/**
 * df_rgx_bus_get_dev_status() - Update current status, including:
 * - stat->current_frequency - Frequency in KHz.
 * - stat->total_time
 * - stat->busy_time
 * Note: total_time and busy_time have arbitrary units, as they are
 * used only as ratios.
 * Utilization is busy_time / total_time .
 */
static int df_rgx_bus_get_dev_status(struct device *dev,
				      struct devfreq_dev_status *stat)
{
	struct busfreq_data *bfdata = dev_get_drvdata(dev);

	DFRGX_DPF(DFRGX_DEBUG_LOW, "%s: entry\n", __func__);

	stat->current_frequency = bfdata->bf_freq_mhz_rlzd * 1000;

	/* FIXME - Compute real utilization statistics. */
	stat->total_time = 100;
	stat->busy_time = 50;

	return 0;
}
/**
 * store_dynamic_turbo_state() - Write function for sysfs
 * sys/devices/platform/dfrgx/dynamic_turbo_state.
 * @dev: platform device.
 * @attr: 1 Attribute associated to this entry.
 * @buf: Buffer to write to.
 */
static ssize_t store_dynamic_turbo_state(struct device *dev,
			struct device_attribute *attr,
			const char *buf, size_t count)
{
	struct busfreq_data *bfdata = dev_get_drvdata(dev);
	int dt_state;
	int ret = -EINVAL;

	ret = sscanf(buf, "%d", &dt_state);

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:  count = %zu, ret = %u , state = %d\n",
				__func__, count, ret, dt_state);
	if (ret != 1)
		goto out;

	dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, dt_state);

	ret = count;
out:
	return ret;
}
/**
 * tcd_set_force_state_override() - thermal cooling device
 * callback set_force_state_override.
 * @tcd: Thermal cooling device structure.
 * @pcs: Pointer to char containing the input values.
 *
 * Invoked via interrupt/callback.
 * Function return value: 0 if success, otherwise -error.
 * Execution context: non-atomic
 */
static int tcd_set_force_state_override(struct thermal_cooling_device *tcd,
	char *buf)
{
	struct busfreq_data *bfdata = (struct busfreq_data *) tcd->devdata;
	unsigned long int freqs[THERMAL_COOLING_DEVICE_MAX_STATE];
	unsigned long int prev_freq = DFRGX_FREQ_533_MHZ;
	int i = 0;

	if (is_tng_a0)
		prev_freq = DFRGX_FREQ_320_MHZ;
	if (df_rgx_is_max_fuse_set())
		prev_freq = DFRGX_FREQ_640_MHZ;

	sscanf(buf, "%lu %lu %lu %lu\n", &freqs[0],
			 &freqs[1],
			 &freqs[2],
			 &freqs[3]);

	DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s values: %lu %lu %lu %lu\n", __func__,
			freqs[0],
			freqs[1],
			freqs[2],
			freqs[3]);

	for (i = 0; (i < THERMAL_COOLING_DEVICE_MAX_STATE) &&
				df_rgx_is_valid_freq(freqs[i]) &&
				prev_freq >= freqs[i]; i++) {
		prev_freq = freqs[i];
	}

	if (i < THERMAL_COOLING_DEVICE_MAX_STATE)
		return -EINVAL;

	for (i = 0; i < THERMAL_COOLING_DEVICE_MAX_STATE; i++)
		bfdata->gpudata[i].freq_limit = freqs[i];

	return 0;
}
/**
 * tcd_set_cur_state() - thermal cooling
 * device callback set_cur_state.
 * @tcd: Thermal cooling device structure.
 * @cs: Input state.
 *
 * Invoked via interrupt/callback.
 * Function return value: 0 if success, otherwise -error.
 * Execution context: non-atomic
 */
static int tcd_set_cur_state(struct thermal_cooling_device *tcd,
	unsigned long cs)
{
	struct busfreq_data *bfdata;
	struct devfreq *df;
	int ret = 0;

	bfdata = (struct busfreq_data *) tcd->devdata;

	if (cs >= THERMAL_COOLING_DEVICE_MAX_STATE)
		cs = THERMAL_COOLING_DEVICE_MAX_STATE - 1;

	/*If different state*/
	if (bfdata->gbp_cooldv_state_cur != cs) {
		int new_index = -1;

		/* Dynamic turbo is not enabled so try
		* to change the state
		*/
		if (!bfdata->g_dfrgx_data.g_enable) {

			if(!df_rgx_is_active()) {
				return -EBUSY;
			}

			/* If thermal state is specified explicitely
			* then suspend burst/unburst thread
			* because the user needs the GPU to run
			* at specific frequency/thermal state level
			*/

			ret = df_rgx_set_freq_khz(bfdata,
				bfdata->gpudata[cs].freq_limit);
			if (ret <= 0)
				return ret;
		} else {
			/* In this case we want to limit the max_freq
			* to the thermal state limit
			*/
			int b_update_freq = 0;
			df = bfdata->devfreq;

			if (!cs) {
				/* We are back in normal operation so set initial values*/
				df->max_freq = DF_RGX_FREQ_KHZ_MAX;
				df->min_freq = DF_RGX_FREQ_KHZ_MIN;
				b_update_freq = 1;
			}
			else {
				dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);
				df->max_freq = bfdata->gpudata[cs].freq_limit;

				if (df->previous_freq > df->max_freq)
					b_update_freq = 1;

				if (bfdata->gpudata[cs].freq_limit < df->min_freq) {
					df->min_freq = bfdata->gpudata[cs].freq_limit;
					new_index = df_rgx_get_util_record_index_by_freq(df->min_freq);

					if (new_index > -1) {
						bfdata->g_dfrgx_data.g_freq_mhz_min = df->min_freq;
						bfdata->g_dfrgx_data.g_min_freq_index = new_index;
					}
					b_update_freq = 1;
				}

				new_index = df_rgx_get_util_record_index_by_freq(df->max_freq);

				if (new_index > -1) {
					bfdata->g_dfrgx_data.g_freq_mhz_max = df->max_freq;
					bfdata->g_dfrgx_data.g_max_freq_index = new_index;
				}

				dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 1);
			}

			if (b_update_freq) {
				/* Pick the min freq this time*/
				bfdata->bf_desired_freq = df->min_freq;
				mutex_lock(&bfdata->lock);
				bfdata->b_need_freq_update = 1;
				mutex_unlock(&bfdata->lock);
			}
		}

		bfdata->gbp_cooldv_state_prev = bfdata->gbp_cooldv_state_cur;
		bfdata->gbp_cooldv_state_cur = cs;

		DFRGX_DPF(DFRGX_DEBUG_HIGH, "Thermal state changed from %d to %d\n",
				bfdata->gbp_cooldv_state_prev,
				bfdata->gbp_cooldv_state_cur);

	}

	return 0;
}
/**
 * df_rgx_bus_target - Request setting of a new frequency.
 * @*p_freq: Input: desired frequency in KHz, output: realized freq in KHz.
 * @flags: DEVFREQ_FLAG_* - not used by this implementation.
 */
static int df_rgx_bus_target(struct device *dev, unsigned long *p_freq,
			      u32 flags)
{
	struct platform_device	*pdev;
	struct busfreq_data	*bfdata;
	struct df_rgx_data_s	*pdfrgx_data;
	struct devfreq		*df;
	unsigned long desired_freq = 0;
	int ret = 0;
	int adjust_curfreq = 0;
	int set_freq = 0;
	(void) flags;

	pdev = container_of(dev, struct platform_device, dev);
	bfdata = platform_get_drvdata(pdev);

	if (bfdata && bfdata->devfreq) {
		int gpu_defer_req = 0;
		df = bfdata->devfreq;
		pdfrgx_data = &bfdata->g_dfrgx_data;
		if (!pdfrgx_data || !pdfrgx_data->g_initialized)
			goto out;

		desired_freq = *p_freq;

		/* Governor changed, will be updated after updatedevfreq() */
		if (strncmp(df->governor->name,
			bfdata->prev_governor, DEVFREQ_NAME_LEN)) {
			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: Governor changed,"
				" prev : %s, current : %s!\n",
				__func__,
				bfdata->prev_governor,
				df->governor->name);

			if (dfrgx_burst_is_enabled(pdfrgx_data))
				dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);

			df_rgx_set_governor_profile(df->governor->name, pdfrgx_data);
			strncpy(bfdata->prev_governor, df->governor->name, DEVFREQ_NAME_LEN);

			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s: Governors should be "
					"the same now, prev : %s, current : %s!\n",
					__func__,
					bfdata->prev_governor,
					df->governor->name);

			set_freq = 1;
		} else if (df->min_freq != pdfrgx_data->g_freq_mhz_min) {
			int new_index = -1;

			if (dfrgx_burst_is_enabled(pdfrgx_data))
				dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);

			new_index = df_rgx_get_util_record_index_by_freq(df->min_freq);
			if (new_index > -1) {
				mutex_lock(&pdfrgx_data->g_mutex_sts);
				pdfrgx_data->g_freq_mhz_min = df->min_freq;
				bfdata->gbp_cooldv_latest_freq_min = df->min_freq;
				pdfrgx_data->g_min_freq_index = new_index;
				mutex_unlock(&pdfrgx_data->g_mutex_sts);
			}

			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:Min freq changed!,"
					" prev_freq %lu, min_freq %lu\n",
					__func__,
					df->previous_freq,
					df->min_freq);

			if (df->previous_freq < df->min_freq) {
				desired_freq = df->min_freq;
				adjust_curfreq = 1;
			}
		} else if (df->max_freq != pdfrgx_data->g_freq_mhz_max) {
			int new_index = -1;

			if (dfrgx_burst_is_enabled(pdfrgx_data))
				dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 0);

			new_index = df_rgx_get_util_record_index_by_freq(df->max_freq);
			if (new_index > -1) {
				mutex_lock(&pdfrgx_data->g_mutex_sts);
				pdfrgx_data->g_freq_mhz_max = df->max_freq;
				bfdata->gbp_cooldv_latest_freq_max = df->max_freq;
				pdfrgx_data->g_max_freq_index = new_index;
				mutex_unlock(&pdfrgx_data->g_mutex_sts);
			}

			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:Max freq changed!,"
				" prev_freq %lu, max_freq %lu\n",
				__func__,
				df->previous_freq,
				df->max_freq);

			if (df->previous_freq > df->max_freq) {
				desired_freq = df->max_freq;
				adjust_curfreq  = 1;
			}
		} else if (!strncmp(df->governor->name,
				"simple_ondemand", DEVFREQ_NAME_LEN)) {
			*p_freq = df->previous_freq;
			goto out;
		}

		/* set_freq changed on userspace governor*/
		if (!strncmp(df->governor->name, "userspace", DEVFREQ_NAME_LEN)) {
			/* update userspace freq*/
			struct userspace_gov_data *data = df->data;

			DFRGX_DPF(DFRGX_DEBUG_HIGH, "%s:userspace governor,"
				" desired %lu, data->user_frequency %lu, input_freq = %lu\n",
				__func__,
				desired_freq,
				data->user_frequency,
				*p_freq);

			data->valid = 1;
			data->user_frequency = desired_freq;
			set_freq = 1;
		}

		if (adjust_curfreq)
			set_freq = 1;

		if (set_freq) {
			/* Freq will be reflected once GPU is back on*/
			if (!df_rgx_is_active()) {
				bfdata->bf_desired_freq = desired_freq;
				mutex_lock(&bfdata->lock);
				bfdata->b_need_freq_update = 1;
				mutex_unlock(&bfdata->lock);
				*p_freq = desired_freq;
				gpu_defer_req = 1;
			} else {
				ret = df_rgx_set_freq_khz(bfdata, desired_freq);
				if (ret > 0) {
					*p_freq = ret;
					ret = 0;
				}
			}
		} else {
			*p_freq = df->previous_freq;
		}

		if ((!strncmp(df->governor->name,
				"simple_ondemand", DEVFREQ_NAME_LEN)
			&& !dfrgx_burst_is_enabled(&bfdata->g_dfrgx_data))
			|| gpu_defer_req)
			dfrgx_burst_set_enable(&bfdata->g_dfrgx_data, 1);
	}

out:
	return ret;
}