/*
 * Copyright (c) 2019 samsung electronics co., ltd.
 *		http://www.samsung.com/
 *
 * Author : Choonghoon Park (choong.park@samsung.com)
 *
 * 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.
 *
 * Exynos DSUFreq driver implementation
 */

#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt

#include <linux/init.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/cpufreq.h>
#include <linux/pm_opp.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sort.h>

#include <uapi/linux/sched/types.h>

#include <soc/samsung/cal-if.h>
#include <soc/samsung/debug-snapshot.h>
#include <soc/samsung/ect_parser.h>
#include <soc/samsung/exynos-acme.h>
#include <soc/samsung/exynos-dm.h>

#define DSUFREQ_ENTRY_INVALID   (~0u)

struct exynos_dsufreq_dm {
	struct list_head		list;
	struct exynos_dm_constraint	c;
};

struct dsufreq_request_table {
	unsigned int			cpu_freq;
	unsigned int			dsu_freq;
};

struct dsufreq_request {
	struct cpumask			cpus;
	unsigned int			idx;
	unsigned int			cur_cpufreq;
	unsigned int			request_dsufreq;
	unsigned int			table_size;

	struct dsufreq_request_table	*cpu_dsu_freq_table;

	struct list_head		list;
};

struct dsufreq_stats {
	unsigned int			table_size;
	unsigned int			last_index;
	unsigned long long		last_time;
	unsigned long long		total_trans;

	unsigned long			*freq_table;
	unsigned long long		*time_in_state;
};

struct dsufreq_domain {
	unsigned int			min_freq;
	unsigned int			max_freq;
	unsigned int			cur_freq;
	unsigned int			min_freq_orig;
	unsigned int			max_freq_orig;
	unsigned int			target_freq;

	unsigned int			state;
	unsigned int			cal_id;
	unsigned int			dm_type;
	unsigned int			dss_type;

	raw_spinlock_t			update_lock;

	/* The next fields are for frequency change work */
	bool				work_in_progress;
	struct work_struct		work;

	struct dsufreq_stats		*stats;
	struct list_head		request_list;

	bool				initialized;

	/* list head of DVFS Manager constraints */
	struct list_head		dm_list;
};

static struct dsufreq_domain dsufreq;

/*********************************************************************
 *                           Helper Function                         *
 *********************************************************************/
static int get_dsufreq_index(struct dsufreq_stats *stats, unsigned int freq)
{
	int index;

	for (index = 0; index < stats->table_size; index++)
		if (stats->freq_table[index] == freq)
			return index;

	return -1;
}

static void update_dsufreq_time_in_state(struct dsufreq_stats *stats)
{
	unsigned long long cur_time = get_jiffies_64();

	stats->time_in_state[stats->last_index] += cur_time - stats->last_time;
	stats->last_time = cur_time;
}

static void update_dsufreq_stats(struct dsufreq_stats *stats, unsigned int freq)
{
	stats->last_index = get_dsufreq_index(stats, freq);
	stats->total_trans++;
}

static inline void dsufreq_verify_within_limits(unsigned int min, unsigned int max)
{
	if (dsufreq.min_freq < min)
		dsufreq.min_freq = min;
	if (dsufreq.max_freq < min)
		dsufreq.max_freq = min;
	if (dsufreq.min_freq > max)
		dsufreq.min_freq = max;
	if (dsufreq.max_freq > max)
		dsufreq.max_freq = max;
	if (dsufreq.min_freq > dsufreq.max_freq)
		dsufreq.min_freq = dsufreq.max_freq;
	return;
}

static inline void
dsufreq_verify_within_cpu_limits(void)
{
	dsufreq_verify_within_limits(dsufreq.min_freq_orig, dsufreq.max_freq_orig);
}

static unsigned int get_target_freq(void)
{
	unsigned int target_freq = 0;
	struct dsufreq_request *cursor_request;

	list_for_each_entry(cursor_request, &dsufreq.request_list, list) {
		if (target_freq < cursor_request->request_dsufreq)
			target_freq = cursor_request->request_dsufreq;
	}

	return target_freq;
}

/* Find lowest freq at or above target in a table in ascending order */
static int dsufreq_table_find_index_al(unsigned int target_freq)
{
	unsigned long *freq_table = dsufreq.stats->freq_table;
	unsigned int best_freq = 0;
	int idx;

	for (idx = 0 ; idx < dsufreq.stats->table_size; idx++) {
		if (freq_table[idx] == DSUFREQ_ENTRY_INVALID)
			continue;

		if (freq_table[idx] >= target_freq)
			return freq_table[idx];

		best_freq = freq_table[idx];
	}

	return best_freq;
}

/* Find highest freq at or below target in a table in ascending order */
static int dsufreq_table_find_index_ah(unsigned int target_freq)
{
	unsigned long *freq_table = dsufreq.stats->freq_table;
	unsigned int best_freq = 0;
	int idx;

	for (idx = 0; idx < dsufreq.stats->table_size; idx++) {
		if (freq_table[idx] == DSUFREQ_ENTRY_INVALID)
			continue;

		if (freq_table[idx] <= target_freq) {
			best_freq = freq_table[idx];
			continue;
		}

		return best_freq;
	}

	return best_freq;
}

static unsigned int resolve_dsufreq(unsigned int target_freq, unsigned int relation)
{
	unsigned int resolve_freq;

	switch (relation) {
	case EXYNOS_DM_RELATION_L:
		resolve_freq = dsufreq_table_find_index_al(target_freq);
		break;
	case EXYNOS_DM_RELATION_H:
		resolve_freq = dsufreq_table_find_index_ah(target_freq);
		break;
	default:
		pr_err("%s: Invalid relation: %d\n", __func__, relation);
		return -EINVAL;
	}

	return resolve_freq;
}

/*********************************************************************
 *                          Scaling Function                         *
 *********************************************************************/
static int scale_dsufreq(unsigned int target_freq, unsigned int relation)
{
	int ret;

	dbg_snapshot_freq(dsufreq.dss_type, dsufreq.cur_freq, target_freq, DSS_FLAG_IN);

	ret = cal_dfs_set_rate(dsufreq.cal_id, target_freq);

	dbg_snapshot_freq(dsufreq.dss_type, dsufreq.cur_freq, target_freq,
			  ret < 0 ? ret : DSS_FLAG_OUT);
	return ret;
}

static void schedule_dsu_work(int target_freq)
{
	unsigned long flags;

	raw_spin_lock_irqsave(&dsufreq.update_lock, flags);

	if (dsufreq.work_in_progress) {
		raw_spin_unlock_irqrestore(&dsufreq.update_lock, flags);
		return;
	}

	if (target_freq < 0)
		dsufreq.target_freq = get_target_freq();
	else
		dsufreq.target_freq = target_freq;

	if (dsufreq.target_freq != dsufreq.cur_freq) {
		dsufreq.work_in_progress = true;
		schedule_work_on(smp_processor_id(), &dsufreq.work);
	}

	raw_spin_unlock_irqrestore(&dsufreq.update_lock, flags);
}

static int dsufreq_cpufreq_scaling_callback(struct notifier_block *nb,
				unsigned long event, void *val)
{
	int index;
	struct cpufreq_freqs *freqs = val;
	struct dsufreq_request *cursor_request, *request = NULL;
	struct dsufreq_request_table * table;

	if (unlikely(!dsufreq.initialized))
		return NOTIFY_DONE;

	switch(event) {
	case CPUFREQ_POSTCHANGE:
		/* Find a request of scaling cluster */
		list_for_each_entry(cursor_request, &dsufreq.request_list, list) {
			if (!cpumask_test_cpu(freqs->policy->cpu, &cursor_request->cpus))
				continue;

			request = cursor_request;
			break;
		}

		if (unlikely(!request))
			break;

		table = request->cpu_dsu_freq_table;
		for (index = 0; index < request->table_size; index++)
			if (table[index].cpu_freq >= freqs->new)
				break;

		if (index < 0)
			index = 0;

		request->cur_cpufreq = table[index].cpu_freq;
		request->request_dsufreq = table[index].dsu_freq;

		/* Check DSUFreq scaling is needed. */
		schedule_dsu_work(-1);

		break;
	default:
		;
	}

	return NOTIFY_DONE;
}

static struct notifier_block dsufreq_cpufreq_scale_notifier = {
	.notifier_call = dsufreq_cpufreq_scaling_callback,
};

/*********************************************************************
 *                      SUPPORT for DVFS MANAGER                     *
 *********************************************************************/
static int dm_scaler(int dm_type, void *devdata, unsigned int target_freq,
						unsigned int relation)
{
	int ret;
	unsigned int resolve_freq;

	resolve_freq = resolve_dsufreq(target_freq, relation);
	if (!resolve_freq) {
		pr_err("%s: Couldn't find proper frequency target_freq %u, resolve_freq %u\n",
				__func__, target_freq, resolve_freq);
		return -EINVAL;
	}

	if (resolve_freq == dsufreq.cur_freq)
		return 0;

	ret = scale_dsufreq(resolve_freq, relation);
	if (ret) {
		pr_err("failed to scale frequency of DSU (%d -> %d)\n",
				dsufreq.cur_freq, resolve_freq);
		return ret;
	}

	update_dsufreq_time_in_state(dsufreq.stats);
	update_dsufreq_stats(dsufreq.stats, resolve_freq);
	dsufreq.cur_freq = resolve_freq;

	return ret;
}

/*********************************************************************
 *                          Sysfs function                           *
 *********************************************************************/
static ssize_t dsufreq_show_min_freq(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, 30, "%d\n", dsufreq.min_freq);
}

static ssize_t dsufreq_store_min_freq(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	int input;

	if (sscanf(buf, "%8d", &input) != 1)
		return -EINVAL;

	dsufreq.min_freq = input;
	dsufreq_verify_within_cpu_limits();

	policy_update_call_to_DM(dsufreq.dm_type, dsufreq.min_freq, dsufreq.max_freq);

	if (dsufreq.cur_freq < input)
		schedule_dsu_work(input);

	return count;
}

static ssize_t dsufreq_show_max_freq(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, 30, "%d\n", dsufreq.max_freq);
}

static ssize_t dsufreq_store_max_freq(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	int input;

	if (sscanf(buf, "%8d", &input) != 1)
		return -EINVAL;

	dsufreq.max_freq = input;
	dsufreq_verify_within_cpu_limits();

	policy_update_call_to_DM(dsufreq.dm_type, dsufreq.min_freq, dsufreq.max_freq);

	if (dsufreq.cur_freq > input)
		schedule_dsu_work(input);

	return count;
}

static ssize_t dsufreq_show_cur_freq(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, 30, "%d\n", dsufreq.cur_freq);
}

static ssize_t dsufreq_show_time_in_state(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	int i;
	ssize_t count = 0;
	struct dsufreq_stats *stats = dsufreq.stats;

	update_dsufreq_time_in_state(stats);

	for (i = 0; i < stats->table_size; i++) {
		if (stats->freq_table[i] == DSUFREQ_ENTRY_INVALID)
			continue;

		count += snprintf(&buf[count], PAGE_SIZE - count, "%u %llu\n",
					stats->freq_table[i],
					(unsigned long long)jiffies_64_to_clock_t(stats->time_in_state[i]));
	}

	return count;
}

static ssize_t dsufreq_show_total_trans(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, 30, "%d\n", dsufreq.stats->total_trans);
}

static DEVICE_ATTR(scaling_min_freq, S_IRUGO | S_IWUSR,
		dsufreq_show_min_freq, dsufreq_store_min_freq);
static DEVICE_ATTR(scaling_max_freq, S_IRUGO | S_IWUSR,
		dsufreq_show_max_freq, dsufreq_store_max_freq);
static DEVICE_ATTR(scaling_cur_freq, S_IRUGO,
		dsufreq_show_cur_freq, NULL);
static DEVICE_ATTR(time_in_state, S_IRUGO,
		dsufreq_show_time_in_state, NULL);
static DEVICE_ATTR(total_trans, S_IRUGO,
		dsufreq_show_total_trans, NULL);

static struct attribute *dsufreq_attrs[] = {
	&dev_attr_scaling_min_freq.attr,
	&dev_attr_scaling_max_freq.attr,
	&dev_attr_scaling_cur_freq.attr,
	&dev_attr_time_in_state.attr,
	&dev_attr_total_trans.attr,
	NULL,
};

static struct attribute_group dsufreq_group = {
	.name = "dsufreq",
	.attrs = dsufreq_attrs,
};

/*********************************************************************
 *                            Init Function                          *
 *********************************************************************/
static void dsufreq_work_fn(struct work_struct *work)
{
	unsigned long freq, flags;

	if (unlikely(!dsufreq.initialized))
		return;

	raw_spin_lock_irqsave(&dsufreq.update_lock, flags);
	freq = (unsigned long)dsufreq.target_freq;
	dsufreq.work_in_progress = false;
	raw_spin_unlock_irqrestore(&dsufreq.update_lock, flags);

	DM_CALL(dsufreq.dm_type, &freq);
}

static int dsufreq_init_domain(struct device_node *dn)
{
	int ret;
	unsigned int val;

	ret = of_property_read_u32(dn, "cal-id", &dsufreq.cal_id);
	if (ret)
		return ret;

	/* Get min/max/cur DSUFreq */
	dsufreq.max_freq = dsufreq.max_freq_orig = cal_dfs_get_max_freq(dsufreq.cal_id);
	dsufreq.min_freq = dsufreq.min_freq_orig = cal_dfs_get_min_freq(dsufreq.cal_id);

	if (!of_property_read_u32(dn, "max-freq", &val))
		dsufreq.max_freq = dsufreq.max_freq_orig = min(dsufreq.max_freq, val);
	if (!of_property_read_u32(dn, "min-freq", &val))
		dsufreq.min_freq = dsufreq.min_freq_orig = max(dsufreq.min_freq, val);

	dsufreq.cur_freq = cal_dfs_get_rate(dsufreq.cal_id);
	WARN_ON(dsufreq.cur_freq < dsufreq.min_freq || dsufreq.max_freq < dsufreq.cur_freq);
	dsufreq.cur_freq = clamp_val(dsufreq.cur_freq, dsufreq.min_freq, dsufreq.max_freq);

	ret = of_property_read_u32(dn, "dss-type", &val);
	if (ret)
		return ret;
	dsufreq.dss_type = val;

	INIT_LIST_HEAD(&dsufreq.request_list);
	raw_spin_lock_init(&dsufreq.update_lock);
	INIT_WORK(&dsufreq.work, dsufreq_work_fn);

	return ret;
}

static int __dsufreq_init_request(struct device_node *dn,
				struct dsufreq_request *req)
{
	int size;
	const char *buf;
	struct dsufreq_request_table *table;
	struct device_node *constraint_dn;

	/* Get cpumask which belongs to this domain */
	if (of_property_read_string(dn, "sibling-cpus", &buf))
		return -EINVAL;
	cpulist_parse(buf, &req->cpus);
	cpumask_and(&req->cpus, &req->cpus, cpu_online_mask);
	if (cpumask_weight(&req->cpus) == 0)
		return -ENODEV;

	constraint_dn = of_parse_phandle(dn, "constraint", 0);
	if (!constraint_dn)
		return -ENODEV;

	/* Get CPU-DSU freq table */
	size = of_property_count_u32_elems(constraint_dn, "table");
	if (size < 0)
		return size;

	table = kzalloc(sizeof(struct dsufreq_request_table) * size / 2, GFP_KERNEL);
	if (!table)
		return -ENOMEM;

	req->table_size = size / 2;

	if (of_property_read_u32_array(constraint_dn, "table", (unsigned int *)table, size))
	        return -EINVAL;

	req->cpu_dsu_freq_table = table;

	return 0;
}

static int dsufreq_init_request(struct device_node *dsufreq_dn)
{
	int idx = 0;
	struct device_node *dn;
	struct dsufreq_request *req;

	for_each_child_of_node(dsufreq_dn, dn) {
		req = kzalloc(sizeof(struct dsufreq_request), GFP_KERNEL);
		if (!req) {
			pr_err("Failed to allocate dsufreq request\n");
			continue;
		}

		req->idx = idx++;
		if (__dsufreq_init_request(dn, req))
			continue;

		list_add(&req->list, &dsufreq.request_list);
	};

	if (list_empty(&dsufreq.request_list))
		return -ENODEV;

	return 0;
}

static int dsufreq_init_freq_table_ect(struct dsufreq_stats *stats)
{
	int index;
	unsigned long *freq_table;

	stats->table_size = cal_dfs_get_lv_num(dsufreq.cal_id);

	/* Get DSUFreq table */
	freq_table = kzalloc(sizeof(unsigned long) * stats->table_size, GFP_KERNEL);
	if (!freq_table)
		return -ENOMEM;
	cal_dfs_get_rate_table(dsufreq.cal_id, freq_table);

	/* Check out invalid frequencies */
	for (index = 0; index < stats->table_size; index++)
		if (freq_table[index] > dsufreq.max_freq
		    || freq_table[index] < dsufreq.min_freq)
			freq_table[index] = DSUFREQ_ENTRY_INVALID;

	stats->freq_table = freq_table;

	return 0;
}

static int dsufreq_init_freq_table_dt(struct device_node *dn, struct dsufreq_stats *stats)
{
	unsigned int freq_table[100];
	int i, size;

	size = of_property_count_u32_elems(dn, "table");
	if (size < 0)
		return size;

	stats->freq_table = kcalloc(size, sizeof(unsigned long), GFP_KERNEL);
	if (!stats->freq_table)
		return -ENOMEM;

	if (of_property_read_u32_array(dn, "table", freq_table, size))
		return -EINVAL;

	stats->table_size = size;

	for (i = 0; i < stats->table_size; i++)
		stats->freq_table[i] = freq_table[i];

	return 0;
}

static int dsufreq_init_stats(struct device_node *dn)
{
	struct dsufreq_stats *stats;
	unsigned long long *time_in_state;
	int init_with_dt;
	int ret = 0;

	stats = kzalloc(sizeof(struct dsufreq_stats), GFP_KERNEL);
	if (!stats)
		return -ENOMEM;

	if (of_property_read_u32(dn, "init-with-dt", &init_with_dt))
		init_with_dt = 0;

	if (init_with_dt)
		ret = dsufreq_init_freq_table_dt(dn, stats);
	else
		ret = dsufreq_init_freq_table_ect(stats);

	if (ret)
		return ret;

	/* Initialize DSUFreq time_in_state */
	time_in_state = kzalloc(sizeof(unsigned long long) * stats->table_size, GFP_KERNEL);
	if (!time_in_state)
		return -ENOMEM;
	stats->time_in_state = time_in_state;

	dsufreq.stats = stats;

	/*
	 * dsufreq table is in ascending order
	 * cur_freq should be at or higher than the requested booting freq(cur_freq)
	 * 0 : Find lowest freq at or above target in a table in ascending order
	 * 1 : Find highest freq at or below target in a table in ascending order
	 */
	dsufreq.cur_freq = resolve_dsufreq(dsufreq.cur_freq, 0);

	stats->last_time = get_jiffies_64();
	stats->last_index = get_dsufreq_index(stats, dsufreq.cur_freq);

	return 0;
}

static int init_constraint_table_ect(struct exynos_dsufreq_dm *dm,
					struct ect_minlock_domain *ect_domain)
{
	unsigned int index;

	for (index = 0; index < ect_domain->num_of_level; index++) {
		dm->c.freq_table[index].master_freq
			= ect_domain->level[index].main_frequencies;
		dm->c.freq_table[index].slave_freq
			= ect_domain->level[index].sub_frequencies;
	}

	return 0;
}

static struct ect_minlock_domain *get_ect_domain(struct device_node *dn)
{
	struct ect_minlock_domain *ect_domain = NULL;
	const char *ect_name;
	void *block;
	int ret;

	ret = of_property_read_string(dn, "ect-name", &ect_name);
	if (ret)
		return NULL;

	block = ect_get_block(BLOCK_MINLOCK);
	if (!block)
		return NULL;

	ect_domain = ect_minlock_get_domain(block, (char *)ect_name);
	if (!ect_domain)
		return NULL;

	return ect_domain;
}

static int dsufreq_init_dm_constraint_table(struct device_node *root)
{
	struct exynos_dsufreq_dm *dm;
	struct of_phandle_iterator iter;
	struct ect_minlock_domain *ect_domain;
	int ret = 0, err;

	/* Initialize list head of DVFS Manager constraints */
	INIT_LIST_HEAD(&dsufreq.dm_list);

	/*
	 * Initialize DVFS Manager constraints
	 * - constraint_type : minimum or maximum constraint
	 * - constraint_dm_type : cpu/mif/int/.. etc
	 * - guidance : constraint from chipset characteristic
	 * - freq_table : constraint table
	 */
	of_for_each_phandle(&iter, err, root, "list", NULL, 0) {
		/* allocate DM constraint */
		dm = kzalloc(sizeof(struct exynos_dsufreq_dm), GFP_KERNEL);
		if (!dm)
			goto init_fail;

		list_add_tail(&dm->list, &dsufreq.dm_list);

		of_property_read_u32(iter.node, "const-type", &dm->c.constraint_type);
		of_property_read_u32(iter.node, "dm-slave", &dm->c.dm_slave);

		if (!of_property_read_bool(iter.node, "guidance"))
			continue;

		dm->c.guidance = true;
		ect_domain = get_ect_domain(iter.node);
		dm->c.table_length = ect_domain->num_of_level;

		dm->c.freq_table = kcalloc(1, sizeof(struct exynos_dm_freq)
					* dm->c.table_length, GFP_KERNEL);
		if (!dm->c.freq_table)
			goto init_fail;

		if (init_constraint_table_ect(dm, ect_domain))
			goto init_fail;

		/*
		 *  dynamic disable for migov control
		 *  skew tables should not be disable
		 */
		if (of_property_read_bool(iter.node, "dynamic-disable"))
			dm->c.support_dynamic_disable = true;

		/* register DM constraint */
		ret = register_exynos_dm_constraint_table(dsufreq.dm_type, &dm->c);
		if (ret)
			goto init_fail;
	}

	return ret;

init_fail:
	while (!list_empty(&dsufreq.dm_list)) {
		dm = list_last_entry(&dsufreq.dm_list,
				struct exynos_dsufreq_dm, list);
		list_del(&dm->list);
		kfree(dm->c.freq_table);
		kfree(dm);
	}

	return ret;
}

static int dsufreq_init_dm(struct device_node *dn)
{
	int ret;

	ret = of_property_read_u32(dn, "dm-type", &dsufreq.dm_type);
	if (ret)
		return ret;

	ret = exynos_dm_data_init(dsufreq.dm_type, &dsufreq,
				dsufreq.min_freq, dsufreq.max_freq, dsufreq.min_freq);
	if (ret)
		return ret;

	dn = of_get_child_by_name(dn, "hw-constraints");
	if (dn) {
		ret = dsufreq_init_dm_constraint_table(dn);
		if (ret)
			return ret;
	}

	return register_exynos_dm_freq_scaler(dsufreq.dm_type, dm_scaler);
}

static int exynos_dsufreq_probe(struct platform_device *pdev)
{
	struct device_node *dn = pdev->dev.of_node;
	int ret = 0;

	/* Explicitly assign false */
	dsufreq.initialized = false;

	ret = dsufreq_init_domain(dn);
	if (ret)
		return ret;

	ret = dsufreq_init_request(dn);
	if (ret)
		return ret;

	ret = dsufreq_init_stats(dn);
	if (ret)
		return ret;

	ret = dsufreq_init_dm(dn);
	if (ret) {
		/*
		 * In case of failure to init dm related data,
		 * need to get notification from CPUFreq to do DSU DVFS.
		 */
		ret = exynos_cpufreq_register_notifier(&dsufreq_cpufreq_scale_notifier,
						       CPUFREQ_TRANSITION_NOTIFIER);
		if (ret)
			return ret;
	}

	ret = sysfs_create_group(&pdev->dev.kobj, &dsufreq_group);
	if (ret)
		return ret;

	dsufreq.initialized = true;

	return ret;
}

static const struct of_device_id of_exynos_dsufreq_match[] = {
	{ .compatible = "samsung,exynos-dsufreq", },
	{ },
};
MODULE_DEVICE_TABLE(of, of_exynos_dsufreq_match);

static struct platform_driver exynos_dsufreq_driver = {
	.driver = {
		.name	= "exynos-dsufreq",
		.owner = THIS_MODULE,
		.of_match_table = of_exynos_dsufreq_match,
	},
	.probe		= exynos_dsufreq_probe,
};

module_platform_driver(exynos_dsufreq_driver);

MODULE_SOFTDEP("pre: exynos-acme");
MODULE_DESCRIPTION("Exynos DSUFreq drvier");
MODULE_LICENSE("GPL");